FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/img2enc.c
Date: 2026-07-19 01:11:25
Exec Total Coverage
Lines: 90 199 45.2%
Functions: 6 7 85.7%
Branches: 50 144 34.7%

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 69 static int write_header(AVFormatContext *s)
92 {
93 69 VideoMuxData *img = s->priv_data;
94 69 AVStream *st = s->streams[0];
95 69 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(st->codecpar->format);
96
97
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69 times.
69 if (st->codecpar->codec_id == AV_CODEC_ID_GIF) {
98 img->muxer = "gif";
99
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69 times.
69 } else if (st->codecpar->codec_id == AV_CODEC_ID_FITS) {
100 img->muxer = "fits";
101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69 times.
69 } else if (st->codecpar->codec_id == AV_CODEC_ID_AV1) {
102 img->muxer = "avif";
103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 69 times.
69 } 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 &&(desc->flags & AV_PIX_FMT_FLAG_PLANAR)
110 && desc->nb_components >= 3;
111 }
112 69 img->img_number = img->start_img_number;
113
114
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 68 times.
69 if (img->update_filemtime) {
115 1 const char *proto = avio_find_protocol_name(s->url);
116 AVDictionaryEntry *entry;
117 int64_t parsed_ts;
118
119
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")) {
120 av_log(s, AV_LOG_WARNING,
121 "update_filemtime is only supported for local files, "
122 "it will be ignored\n");
123 img->update_filemtime = 0;
124 } else {
125 1 entry = av_dict_get(s->metadata, "creation_time", NULL, 0);
126
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) {
127 av_log(s, AV_LOG_WARNING,
128 "No valid creation_time metadata found, "
129 "update_filemtime will be ignored\n");
130 img->update_filemtime = 0;
131 } else {
132 1 img->creation_ts = parsed_ts;
133 }
134 }
135 }
136
137 69 return 0;
138 }
139
140 static int write_muxed_file(AVFormatContext *s, AVIOContext *pb, AVPacket *pkt)
141 {
142 VideoMuxData *img = s->priv_data;
143 AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
144 AVStream *st;
145 AVPacket *const pkt2 = ffformatcontext(s)->pkt;
146 AVFormatContext *fmt = NULL;
147 int ret;
148
149 /* URL is not used directly as we are overriding the IO context later. */
150 ret = avformat_alloc_output_context2(&fmt, NULL, img->muxer, s->url);
151 if (ret < 0)
152 return ret;
153 st = avformat_new_stream(fmt, NULL);
154 if (!st) {
155 ret = AVERROR(ENOMEM);
156 goto out;
157 }
158 st->id = pkt->stream_index;
159
160 fmt->pb = pb;
161
162 ret = av_packet_ref(pkt2, pkt);
163 if (ret < 0)
164 goto out;
165 pkt2->stream_index = 0;
166
167 if ((ret = avcodec_parameters_copy(st->codecpar, par)) < 0 ||
168 (ret = avformat_write_header(fmt, NULL)) < 0 ||
169 (ret = av_interleaved_write_frame(fmt, pkt2)) < 0 ||
170 (ret = av_write_trailer(fmt))) {}
171
172 av_packet_unref(pkt2);
173 out:
174 avformat_free_context(fmt);
175 return ret;
176 }
177
178 151 static int write_packet_pipe(AVFormatContext *s, AVPacket *pkt)
179 {
180 151 VideoMuxData *img = s->priv_data;
181
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 151 times.
151 if (img->muxer) {
182 int ret = write_muxed_file(s, s->pb, pkt);
183 if (ret < 0)
184 return ret;
185 } else {
186 151 avio_write(s->pb, pkt->data, pkt->size);
187 }
188 151 img->img_number++;
189 151 return 0;
190 }
191
192 676 static int write_and_close(AVFormatContext *s, AVIOContext **pb, const unsigned char *buf, int size)
193 {
194 676 avio_write(*pb, buf, size);
195 676 avio_flush(*pb);
196 676 return ff_format_io_close(s, pb);
197 }
198
199 676 static int write_packet(AVFormatContext *s, AVPacket *pkt)
200 {
201 676 VideoMuxData *img = s->priv_data;
202 676 AVIOContext *pb[4] = {0};
203 676 char* target[4] = {0};
204 676 char* tmp[4] = {0};
205 676 char* filepaths[4] = {0};
206 676 AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
207 676 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(par->format);
208 int ret, i;
209 676 AVDictionary *options = NULL;
210 AVBPrint filename;
211 676 av_bprint_init(&filename, 0, AV_BPRINT_SIZE_UNLIMITED);
212
213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
676 if (img->update) {
214 av_bprintf(&filename, "%s", s->url);
215
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
676 } else if (img->use_strftime) {
216 time_t now0;
217 struct tm *tm, tmpbuf;
218 time(&now0);
219 tm = localtime_r(&now0, &tmpbuf);
220 av_bprint_strftime(&filename, s->url, tm);
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
676 } else if (img->frame_pts) {
222 if (ff_bprint_get_frame_filename(&filename, s->url, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) {
223 av_log(s, AV_LOG_ERROR, "Cannot write filename by pts of the frames.");
224 ret = AVERROR(EINVAL);
225 goto fail;
226 }
227
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 666 times.
676 } else if (ff_bprint_get_frame_filename(&filename, s->url,
228 676 img->img_number,
229 AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) {
230
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (img->img_number == img->start_img_number) {
231 10 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);
232 10 av_log(s, AV_LOG_WARNING,
233 "Use a pattern such as %%03d for an image sequence or "
234 "use the -update option (with -frames:v 1 if needed) to write a single image.\n");
235 10 av_bprint_clear(&filename);
236 10 av_bprintf(&filename, "%s", s->url);
237 } else {
238 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");
239 ret = AVERROR(EINVAL);
240 goto fail;
241 }
242 }
243
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 676 times.
676 if (!av_bprint_is_complete(&filename)) {
244 ret = AVERROR(ENOMEM);
245 goto fail;
246 }
247
1/2
✓ Branch 0 taken 676 times.
✗ Branch 1 not taken.
676 for (i = 0; i < 4; i++) {
248 676 av_dict_copy(&options, img->protocol_opts, 0);
249
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
676 if (img->use_rename) {
250 tmp[i] = av_asprintf("%s.tmp", filename.str);
251 target[i] = av_strdup(filename.str);
252 if (!tmp[i] || !target[i]) {
253 ret = AVERROR(ENOMEM);
254 goto fail;
255 }
256 }
257
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 676 times.
676 if ((ret = s->io_open(s, &pb[i], tmp[i] ? tmp[i] : filename.str, AVIO_FLAG_WRITE, &options)) < 0) {
258 av_log(s, AV_LOG_ERROR, "Could not open file : %s\n", tmp[i] ? tmp[i] : filename.str);
259 goto fail;
260 }
261
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
676 if (options) {
262 av_log(s, AV_LOG_ERROR, "Could not recognize some protocol options\n");
263 ret = AVERROR(EINVAL);
264 goto fail;
265 }
266
267
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 673 times.
676 if (img->update_filemtime) {
268 3 filepaths[i] = av_strdup(filename.str);
269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!filepaths[i]) {
270 ret = AVERROR(ENOMEM);
271 goto fail;
272 }
273 }
274
275
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
676 if (!img->split_planes || i+1 >= desc->nb_components)
276 break;
277 filename.str[filename.len - 1] = "UVAx"[i];
278 }
279 676 av_bprint_finalize(&filename, NULL);
280
281
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
676 if (img->split_planes) {
282 int ysize = par->width * par->height;
283 int usize = AV_CEIL_RSHIFT(par->width, desc->log2_chroma_w) * AV_CEIL_RSHIFT(par->height, desc->log2_chroma_h);
284 if (desc->comp[0].depth >= 9) {
285 ysize *= 2;
286 usize *= 2;
287 }
288 if (ysize + 2*usize + (desc->nb_components > 3) * ysize > pkt->size) {
289 ret = AVERROR(EINVAL);
290 goto fail;
291 }
292
293 if ((ret = write_and_close(s, &pb[0], pkt->data , ysize)) < 0 ||
294 (ret = write_and_close(s, &pb[1], pkt->data + ysize , usize)) < 0 ||
295 (ret = write_and_close(s, &pb[2], pkt->data + ysize + usize, usize)) < 0)
296 goto fail;
297 if (desc->nb_components > 3)
298 ret = write_and_close(s, &pb[3], pkt->data + ysize + 2*usize, ysize);
299
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
676 } else if (img->muxer) {
300 if ((ret = write_muxed_file(s, pb[0], pkt)) < 0)
301 goto fail;
302 ret = ff_format_io_close(s, &pb[0]);
303 } else {
304 676 ret = write_and_close(s, &pb[0], pkt->data, pkt->size);
305 }
306
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 676 times.
676 if (ret < 0)
307 goto fail;
308
309
2/4
✓ Branch 0 taken 676 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 676 times.
676 for (i = 0; i < 4 && tmp[i]; i++) {
310 int ret = ff_rename(tmp[i], target[i], s);
311 if (ret < 0)
312 goto fail;
313 av_freep(&tmp[i]);
314 av_freep(&target[i]);
315 }
316
317
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 673 times.
676 if (img->update_filemtime) {
318 3 AVStream *st = s->streams[pkt->stream_index];
319 3 int64_t frame_ts = img->creation_ts;
320 3 int skip = 0;
321
322
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (pkt->pts != AV_NOPTS_VALUE) {
323 3 int64_t offset = av_rescale_q(pkt->pts, st->time_base,
324 3 AV_TIME_BASE_Q);
325
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 ||
326
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) ||
327 (offset < 0 && img->creation_ts < INT64_MIN - offset)) {
328 av_log(s, AV_LOG_WARNING,
329 "Integer overflow computing file mtime, skipping\n");
330 skip = 1;
331 } else {
332 3 frame_ts += offset;
333 }
334 }
335
336
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (!skip) {
337
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++)
338 3 set_file_mtime(s, filepaths[i], frame_ts);
339 }
340 }
341
342
2/2
✓ Branch 0 taken 2704 times.
✓ Branch 1 taken 676 times.
3380 for (i = 0; i < FF_ARRAY_ELEMS(filepaths); i++)
343 2704 av_freep(&filepaths[i]);
344
345 676 img->img_number++;
346 676 return 0;
347
348 fail:
349 av_bprint_finalize(&filename, NULL);
350 av_dict_free(&options);
351 for (i = 0; i < FF_ARRAY_ELEMS(pb); i++) {
352 av_freep(&tmp[i]);
353 av_freep(&target[i]);
354 av_freep(&filepaths[i]);
355 if (pb[i])
356 ff_format_io_close(s, &pb[i]);
357 }
358 return ret;
359 }
360
361 69 static int query_codec(enum AVCodecID id, int std_compliance)
362 {
363 int i;
364
1/2
✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
69 for (i = 0; ff_img_tags[i].id != AV_CODEC_ID_NONE; i++)
365
1/2
✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
69 if (ff_img_tags[i].id == id)
366 69 return 1;
367
368 // Anything really can be stored in img2
369 return std_compliance < FF_COMPLIANCE_NORMAL;
370 }
371
372 #define OFFSET(x) offsetof(VideoMuxData, x)
373 #define ENC AV_OPT_FLAG_ENCODING_PARAM
374 static const AVOption muxoptions[] = {
375 { "update", "continuously overwrite one file", OFFSET(update), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
376 { "start_number", "set first number in the sequence", OFFSET(start_img_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, INT_MAX, ENC },
377 { "strftime", "use strftime for filename", OFFSET(use_strftime), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
378 { "frame_pts", "use current frame pts for filename", OFFSET(frame_pts), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
379 { "atomic_writing", "write files atomically (using temporary files and renames)", OFFSET(use_rename), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
380 { "protocol_opts", "specify protocol options for the opened files", OFFSET(protocol_opts), AV_OPT_TYPE_DICT, {0}, 0, 0, ENC },
381 { "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 },
382 { NULL },
383 };
384
385 #if CONFIG_IMAGE2_MUXER
386 static const AVClass img2mux_class = {
387 .class_name = "image2 muxer",
388 .item_name = av_default_item_name,
389 .option = muxoptions,
390 .version = LIBAVUTIL_VERSION_INT,
391 };
392
393 const FFOutputFormat ff_image2_muxer = {
394 .p.name = "image2",
395 .p.long_name = NULL_IF_CONFIG_SMALL("image2 sequence"),
396 .p.extensions = "bmp,dpx,exr,jls,jpeg,jpg,jxs,jxl,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,phm,"
397 "png,ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,"
398 "im24,sunras,vbn,xbm,xface,pix,y,avif,qoi,hdr,wbmp",
399 .priv_data_size = sizeof(VideoMuxData),
400 .p.video_codec = AV_CODEC_ID_MJPEG,
401 .write_header = write_header,
402 .write_packet = write_packet,
403 .query_codec = query_codec,
404 .p.flags = AVFMT_NOTIMESTAMPS | AVFMT_NODIMENSIONS | AVFMT_NOFILE,
405 .p.priv_class = &img2mux_class,
406 };
407 #endif
408 #if CONFIG_IMAGE2PIPE_MUXER
409 const FFOutputFormat ff_image2pipe_muxer = {
410 .p.name = "image2pipe",
411 .p.long_name = NULL_IF_CONFIG_SMALL("piped image2 sequence"),
412 .priv_data_size = sizeof(VideoMuxData),
413 .p.video_codec = AV_CODEC_ID_MJPEG,
414 .write_header = write_header,
415 .write_packet = write_packet_pipe,
416 .query_codec = query_codec,
417 .p.flags = AVFMT_NOTIMESTAMPS | AVFMT_NODIMENSIONS
418 };
419 #endif
420