FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/concatdec.c
Date: 2026-04-22 18:56:46
Exec Total Coverage
Lines: 307 536 57.3%
Functions: 17 21 81.0%
Branches: 159 367 43.3%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2012 Nicolas George
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 License
8 * 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
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "libavutil/attributes_internal.h"
22 #include "libavutil/avstring.h"
23 #include "libavutil/avassert.h"
24 #include "libavutil/bprint.h"
25 #include "libavutil/intreadwrite.h"
26 #include "libavutil/mem.h"
27 #include "libavutil/opt.h"
28 #include "libavutil/parseutils.h"
29 #include "libavutil/timestamp.h"
30 #include "libavcodec/codec_desc.h"
31 #include "libavcodec/bsf.h"
32 #include "avformat.h"
33 #include "avio_internal.h"
34 #include "demux.h"
35 #include "internal.h"
36 #include "url.h"
37
38 typedef enum ConcatMatchMode {
39 MATCH_ONE_TO_ONE,
40 MATCH_EXACT_ID,
41 } ConcatMatchMode;
42
43 typedef struct ConcatStream {
44 AVBSFContext *bsf;
45 int out_stream_index;
46 } ConcatStream;
47
48 typedef struct {
49 char *url;
50 int64_t start_time;
51 int64_t file_start_time;
52 int64_t file_inpoint;
53 int64_t duration;
54 int64_t user_duration;
55 int64_t next_dts;
56 ConcatStream *streams;
57 int64_t inpoint;
58 int64_t outpoint;
59 AVDictionary *metadata;
60 AVDictionary *options;
61 int nb_streams;
62 } ConcatFile;
63
64 typedef struct {
65 AVClass *class;
66 ConcatFile *files;
67 ConcatFile *cur_file;
68 unsigned nb_files;
69 AVFormatContext *avf;
70 int safe;
71 int seekable;
72 int eof;
73 ConcatMatchMode stream_match_mode;
74 unsigned auto_convert;
75 int segment_time_metadata;
76 } ConcatContext;
77
78 7474 static int concat_probe(const AVProbeData *probe)
79 {
80 7474 return memcmp(probe->buf, "ffconcat version 1.0", 20) ?
81
2/2
✓ Branch 0 taken 7469 times.
✓ Branch 1 taken 5 times.
7474 0 : AVPROBE_SCORE_MAX;
82 }
83
84 402 static char *get_keyword(uint8_t **cursor)
85 {
86 402 char *ret = *cursor += strspn(*cursor, SPACE_CHARS);
87 402 *cursor += strcspn(*cursor, SPACE_CHARS);
88
2/2
✓ Branch 0 taken 210 times.
✓ Branch 1 taken 192 times.
402 if (**cursor) {
89 210 *((*cursor)++) = 0;
90 210 *cursor += strspn(*cursor, SPACE_CHARS);
91 }
92 402 return ret;
93 }
94
95 static int safe_filename(const char *f)
96 {
97 const char *start = f;
98
99 for (; *f; f++) {
100 /* A-Za-z0-9_- */
101 if (!((unsigned)((*f | 32) - 'a') < 26 ||
102 (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) {
103 if (f == start)
104 return 0;
105 else if (*f == '/')
106 start = f + 1;
107 else if (*f != '.')
108 return 0;
109 }
110 }
111 return 1;
112 }
113
114 #define FAIL(retcode) do { ret = (retcode); goto fail; } while(0)
115
116 68 static int add_file(AVFormatContext *avf, char *filename, ConcatFile **rfile,
117 unsigned *nb_files_alloc)
118 {
119 68 ConcatContext *cat = avf->priv_data;
120 ConcatFile *file;
121 68 char *url = NULL;
122 const char *proto;
123 const char *ptr;
124 size_t url_len;
125 int ret;
126
127
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
68 if (cat->safe && !safe_filename(filename)) {
128 av_log(avf, AV_LOG_ERROR, "Unsafe file name '%s'\n", filename);
129 FAIL(AVERROR(EPERM));
130 }
131
132 68 proto = avio_find_protocol_name(filename);
133
2/4
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 68 times.
68 if (proto && av_strstart(filename, proto, &ptr) &&
134 (*ptr == ':' || *ptr == ',')) {
135 url = filename;
136 filename = NULL;
137 } else {
138 68 url_len = strlen(avf->url) + strlen(filename) + 16;
139
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
68 if (!(url = av_malloc(url_len)))
140 FAIL(AVERROR(ENOMEM));
141 68 ff_make_absolute_url(url, url_len, avf->url, filename);
142 68 av_freep(&filename);
143 }
144
145
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 61 times.
68 if (cat->nb_files >= *nb_files_alloc) {
146 7 size_t n = FFMAX(*nb_files_alloc * 2, 16);
147 ConcatFile *new_files;
148
3/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
14 if (n <= cat->nb_files || n > SIZE_MAX / sizeof(*cat->files) ||
149 7 !(new_files = av_realloc(cat->files, n * sizeof(*cat->files))))
150 FAIL(AVERROR(ENOMEM));
151 7 cat->files = new_files;
152 7 *nb_files_alloc = n;
153 }
154
155 68 file = &cat->files[cat->nb_files++];
156 68 memset(file, 0, sizeof(*file));
157 68 *rfile = file;
158
159 68 file->url = url;
160 68 file->start_time = AV_NOPTS_VALUE;
161 68 file->duration = AV_NOPTS_VALUE;
162 68 file->next_dts = AV_NOPTS_VALUE;
163 68 file->inpoint = AV_NOPTS_VALUE;
164 68 file->outpoint = AV_NOPTS_VALUE;
165 68 file->user_duration = AV_NOPTS_VALUE;
166
167 68 return 0;
168
169 fail:
170 av_free(url);
171 av_free(filename);
172 return ret;
173 }
174
175 136 static int copy_stream_props(AVStream *st, AVStream *source_st)
176 {
177 int ret;
178
179
3/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
136 if (st->codecpar->codec_id || !source_st->codecpar->codec_id) {
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 126 times.
126 if (st->codecpar->extradata_size < source_st->codecpar->extradata_size) {
181 ret = ff_alloc_extradata(st->codecpar,
182 source_st->codecpar->extradata_size);
183 if (ret < 0)
184 return ret;
185 }
186
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 63 times.
126 if (source_st->codecpar->extradata_size)
187 63 memcpy(st->codecpar->extradata, source_st->codecpar->extradata,
188 63 source_st->codecpar->extradata_size);
189 126 return 0;
190 }
191
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if ((ret = avcodec_parameters_copy(st->codecpar, source_st->codecpar)) < 0)
192 return ret;
193 10 st->r_frame_rate = source_st->r_frame_rate;
194 10 st->avg_frame_rate = source_st->avg_frame_rate;
195 10 st->sample_aspect_ratio = source_st->sample_aspect_ratio;
196 10 avpriv_set_pts_info(st, 64, source_st->time_base.num, source_st->time_base.den);
197
198 10 av_dict_copy(&st->metadata, source_st->metadata, 0);
199 10 return 0;
200 }
201
202 136 static int detect_stream_specific(AVFormatContext *avf, int idx)
203 {
204 136 ConcatContext *cat = avf->priv_data;
205 136 AVStream *st = cat->avf->streams[idx];
206 136 ConcatStream *cs = &cat->cur_file->streams[idx];
207 const AVBitStreamFilter *filter;
208 AVBSFContext *bsf;
209 int ret;
210
211
2/4
✓ Branch 0 taken 136 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 136 times.
136 if (cat->auto_convert && st->codecpar->codec_id == AV_CODEC_ID_H264) {
212 if (!st->codecpar->extradata_size ||
213 (st->codecpar->extradata_size >= 3 && AV_RB24(st->codecpar->extradata) == 1) ||
214 (st->codecpar->extradata_size >= 4 && AV_RB32(st->codecpar->extradata) == 1))
215 return 0;
216 av_log(cat->avf, AV_LOG_INFO,
217 "Auto-inserting h264_mp4toannexb bitstream filter\n");
218 filter = av_bsf_get_by_name("h264_mp4toannexb");
219 if (!filter) {
220 av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb bitstream filter "
221 "required for H.264 streams\n");
222 return AVERROR_BSF_NOT_FOUND;
223 }
224 ret = av_bsf_alloc(filter, &bsf);
225 if (ret < 0)
226 return ret;
227 cs->bsf = bsf;
228
229 ret = avcodec_parameters_copy(bsf->par_in, st->codecpar);
230 if (ret < 0)
231 return ret;
232
233 ret = av_bsf_init(bsf);
234 if (ret < 0)
235 return ret;
236
237 ret = avcodec_parameters_copy(st->codecpar, bsf->par_out);
238 if (ret < 0)
239 return ret;
240 }
241 136 return 0;
242 }
243
244 64 static int match_streams_one_to_one(AVFormatContext *avf)
245 {
246 64 ConcatContext *cat = avf->priv_data;
247 AVStream *st;
248 int i, ret;
249
250
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 64 times.
192 for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
251
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 8 times.
128 if (i < avf->nb_streams) {
252 120 st = avf->streams[i];
253 } else {
254
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (!(st = avformat_new_stream(avf, NULL)))
255 return AVERROR(ENOMEM);
256 }
257
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
128 if ((ret = copy_stream_props(st, cat->avf->streams[i])) < 0)
258 return ret;
259 128 cat->cur_file->streams[i].out_stream_index = i;
260 }
261 64 return 0;
262 }
263
264 4 static int match_streams_exact_id(AVFormatContext *avf)
265 {
266 4 ConcatContext *cat = avf->priv_data;
267 AVStream *st;
268 int i, j, ret;
269
270
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
271 8 st = cat->avf->streams[i];
272
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 8 times.
24 for (j = 0; j < avf->nb_streams; j++) {
273
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
16 if (avf->streams[j]->id == st->id) {
274 8 av_log(avf, AV_LOG_VERBOSE,
275 "Match slave stream #%d with stream #%d id 0x%x\n",
276 i, j, st->id);
277
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if ((ret = copy_stream_props(avf->streams[j], st)) < 0)
278 return ret;
279 8 cat->cur_file->streams[i].out_stream_index = j;
280 }
281 }
282 }
283 4 return 0;
284 }
285
286 1202 static int match_streams(AVFormatContext *avf)
287 {
288 1202 ConcatContext *cat = avf->priv_data;
289 ConcatStream *map;
290 int i, ret;
291
292
2/2
✓ Branch 0 taken 1134 times.
✓ Branch 1 taken 68 times.
1202 if (cat->cur_file->nb_streams >= cat->avf->nb_streams)
293 1134 return 0;
294 68 map = av_realloc(cat->cur_file->streams,
295 68 cat->avf->nb_streams * sizeof(*map));
296
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (!map)
297 return AVERROR(ENOMEM);
298 68 cat->cur_file->streams = map;
299 68 memset(map + cat->cur_file->nb_streams, 0,
300 68 (cat->avf->nb_streams - cat->cur_file->nb_streams) * sizeof(*map));
301
302
2/2
✓ Branch 0 taken 136 times.
✓ Branch 1 taken 68 times.
204 for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
303 136 map[i].out_stream_index = -1;
304
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 136 times.
136 if ((ret = detect_stream_specific(avf, i)) < 0)
305 return ret;
306 }
307
2/3
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
68 switch (cat->stream_match_mode) {
308 64 case MATCH_ONE_TO_ONE:
309 64 ret = match_streams_one_to_one(avf);
310 64 break;
311 4 case MATCH_EXACT_ID:
312 4 ret = match_streams_exact_id(avf);
313 4 break;
314 default:
315 ret = AVERROR_BUG;
316 }
317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (ret < 0)
318 return ret;
319 68 cat->cur_file->nb_streams = cat->avf->nb_streams;
320 68 return 0;
321 }
322
323 136 static int64_t get_best_effort_duration(ConcatFile *file, AVFormatContext *avf)
324 {
325
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 130 times.
136 if (file->user_duration != AV_NOPTS_VALUE)
326 6 return file->user_duration;
327
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 20 times.
130 if (file->outpoint != AV_NOPTS_VALUE)
328 110 return av_sat_sub64(file->outpoint, file->file_inpoint);
329
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if (avf->duration > 0)
330 20 return av_sat_sub64(avf->duration, file->file_inpoint - file->file_start_time);
331 if (file->next_dts != AV_NOPTS_VALUE)
332 return file->next_dts - file->file_inpoint;
333 return AV_NOPTS_VALUE;
334 }
335
336 68 static int open_file(AVFormatContext *avf, unsigned fileno)
337 {
338 68 ConcatContext *cat = avf->priv_data;
339 68 ConcatFile *file = &cat->files[fileno];
340 68 AVDictionary *options = NULL;
341 int ret;
342
343
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 5 times.
68 if (cat->avf)
344 63 avformat_close_input(&cat->avf);
345
346 68 cat->avf = avformat_alloc_context();
347
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (!cat->avf)
348 return AVERROR(ENOMEM);
349
350 68 cat->avf->flags |= avf->flags & ~AVFMT_FLAG_CUSTOM_IO;
351 68 cat->avf->interrupt_callback = avf->interrupt_callback;
352
353
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
68 if ((ret = ff_copy_whiteblacklists(cat->avf, avf)) < 0)
354 return ret;
355
356 68 ret = av_dict_copy(&options, file->options, 0);
357
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (ret < 0)
358 return ret;
359
360
2/4
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 68 times.
136 if ((ret = avformat_open_input(&cat->avf, file->url, NULL, &options)) < 0 ||
361 68 (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) {
362 av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url);
363 av_dict_free(&options);
364 avformat_close_input(&cat->avf);
365 return ret;
366 }
367
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (options) {
368 av_log(avf, AV_LOG_WARNING, "Unused options for '%s'.\n", file->url);
369 /* TODO log unused options once we have a proper string API */
370 av_dict_free(&options);
371 }
372 68 cat->cur_file = file;
373
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 5 times.
68 file->start_time = !fileno ? 0 :
374 63 cat->files[fileno - 1].start_time +
375 63 cat->files[fileno - 1].duration;
376
1/2
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
68 file->file_start_time = (cat->avf->start_time == AV_NOPTS_VALUE) ? 0 : cat->avf->start_time;
377
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 58 times.
68 file->file_inpoint = (file->inpoint == AV_NOPTS_VALUE) ? file->file_start_time : file->inpoint;
378 68 file->duration = get_best_effort_duration(file, cat->avf);
379
380
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (cat->segment_time_metadata) {
381 av_dict_set_int(&file->metadata, "lavf.concatdec.start_time", file->start_time, 0);
382 if (file->duration != AV_NOPTS_VALUE)
383 av_dict_set_int(&file->metadata, "lavf.concatdec.duration", file->duration, 0);
384 }
385
386
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
68 if ((ret = match_streams(avf)) < 0)
387 return ret;
388
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 10 times.
68 if (file->inpoint != AV_NOPTS_VALUE) {
389
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 58 times.
58 if ((ret = avformat_seek_file(cat->avf, -1, INT64_MIN, file->inpoint, file->inpoint, 0)) < 0)
390 return ret;
391 }
392 68 return 0;
393 }
394
395 5 static int concat_read_close(AVFormatContext *avf)
396 {
397 5 ConcatContext *cat = avf->priv_data;
398 unsigned i, j;
399
400
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 5 times.
73 for (i = 0; i < cat->nb_files; i++) {
401 68 av_freep(&cat->files[i].url);
402
2/2
✓ Branch 0 taken 136 times.
✓ Branch 1 taken 68 times.
204 for (j = 0; j < cat->files[i].nb_streams; j++) {
403
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 136 times.
136 if (cat->files[i].streams[j].bsf)
404 av_bsf_free(&cat->files[i].streams[j].bsf);
405 }
406 68 av_freep(&cat->files[i].streams);
407 68 av_dict_free(&cat->files[i].metadata);
408 68 av_dict_free(&cat->files[i].options);
409 }
410
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (cat->avf)
411 5 avformat_close_input(&cat->avf);
412 5 av_freep(&cat->files);
413 5 return 0;
414 }
415
416 #define MAX_ARGS 3
417 #define NEEDS_UNSAFE (1 << 0)
418 #define NEEDS_FILE (1 << 1)
419 #define NEEDS_STREAM (1 << 2)
420
421 typedef struct ParseSyntax {
422 const char *keyword;
423 attribute_nonstring char args[MAX_ARGS];
424 uint8_t flags;
425 } ParseSyntax;
426
427 typedef enum ParseDirective {
428 DIR_FFCONCAT,
429 DIR_FILE,
430 DIR_DURATION,
431 DIR_INPOINT,
432 DIR_OUTPOINT,
433 DIR_FPMETA,
434 DIR_FPMETAS,
435 DIR_OPTION,
436 DIR_STREAM,
437 DIR_EXSID,
438 DIR_STMETA,
439 DIR_STCODEC,
440 DIR_STEDATA,
441 DIR_CHAPTER,
442 } ParseDirective;
443
444 static const ParseSyntax syntax[] = {
445 [DIR_FFCONCAT ] = { "ffconcat", "kk", 0 },
446 [DIR_FILE ] = { "file", "s", 0 },
447 [DIR_DURATION ] = { "duration", "d", NEEDS_FILE },
448 [DIR_INPOINT ] = { "inpoint", "d", NEEDS_FILE },
449 [DIR_OUTPOINT ] = { "outpoint", "d", NEEDS_FILE },
450 [DIR_FPMETA ] = { "file_packet_meta", "ks", NEEDS_FILE },
451 [DIR_FPMETAS ] = { "file_packet_metadata", "s", NEEDS_FILE },
452 [DIR_OPTION ] = { "option", "ks", NEEDS_FILE | NEEDS_UNSAFE },
453 [DIR_STREAM ] = { "stream", "", 0 },
454 [DIR_EXSID ] = { "exact_stream_id", "i", NEEDS_STREAM },
455 [DIR_STMETA ] = { "stream_meta", "ks", NEEDS_STREAM },
456 [DIR_STCODEC ] = { "stream_codec", "k", NEEDS_STREAM },
457 [DIR_STEDATA ] = { "stream_extradata", "k", NEEDS_STREAM },
458 [DIR_CHAPTER ] = { "chapter", "idd", 0 },
459 };
460
461 5 static int concat_parse_script(AVFormatContext *avf)
462 {
463 5 ConcatContext *cat = avf->priv_data;
464 5 unsigned nb_files_alloc = 0;
465 AVBPrint bp;
466 uint8_t *cursor, *keyword;
467 5 ConcatFile *file = NULL;
468 5 AVStream *stream = NULL;
469 5 AVChapter *chapter = NULL;
470 5 unsigned line = 0, arg;
471 const ParseSyntax *dir;
472 char *arg_kw[MAX_ARGS];
473 5 char *arg_str[MAX_ARGS] = { 0 };
474 int64_t arg_int[MAX_ARGS];
475 int ret;
476
477 5 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
478
479
2/2
✓ Branch 1 taken 268 times.
✓ Branch 2 taken 5 times.
273 while ((ret = ff_read_line_to_bprint_overwrite(avf->pb, &bp)) >= 0) {
480 268 line++;
481 268 cursor = bp.str;
482 268 keyword = get_keyword(&cursor);
483
3/4
✓ Branch 0 taken 199 times.
✓ Branch 1 taken 69 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 199 times.
268 if (!*keyword || *keyword == '#')
484 69 continue;
485
1/2
✓ Branch 0 taken 741 times.
✗ Branch 1 not taken.
741 for (dir = syntax; dir < syntax + FF_ARRAY_ELEMS(syntax); dir++)
486
2/2
✓ Branch 0 taken 199 times.
✓ Branch 1 taken 542 times.
741 if (!strcmp(dir->keyword, keyword))
487 199 break;
488
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 199 times.
199 if (dir >= syntax + FF_ARRAY_ELEMS(syntax)) {
489 av_log(avf, AV_LOG_ERROR, "Line %d: unknown keyword '%s'\n",
490 line, keyword);
491 FAIL(AVERROR_INVALIDDATA);
492 }
493
494 /* Flags check */
495
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 199 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
199 if ((dir->flags & NEEDS_UNSAFE) && cat->safe) {
496 av_log(avf, AV_LOG_ERROR, "Line %d: %s not allowed if safe\n", line, keyword);
497 FAIL(AVERROR_INVALIDDATA);
498 }
499
3/4
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 79 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 120 times.
199 if ((dir->flags & NEEDS_FILE) && !cat->nb_files) {
500 av_log(avf, AV_LOG_ERROR, "Line %d: %s without file\n", line, keyword);
501 FAIL(AVERROR_INVALIDDATA);
502 }
503
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 195 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
199 if ((dir->flags & NEEDS_STREAM) && !avf->nb_streams) {
504 av_log(avf, AV_LOG_ERROR, "Line %d: %s without stream\n", line, keyword);
505 FAIL(AVERROR_INVALIDDATA);
506 }
507
508 /* Arguments parsing */
509
3/4
✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 208 times.
✓ Branch 3 taken 199 times.
407 for (arg = 0; arg < FF_ARRAY_ELEMS(dir->args) && dir->args[arg]; arg++) {
510
4/5
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 74 times.
✗ Branch 4 not taken.
208 switch (dir->args[arg]) {
511 116 case 'd': /* duration */
512 116 arg_kw[arg] = get_keyword(&cursor);
513 116 ret = av_parse_time(&arg_int[arg], arg_kw[arg], 1);
514
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 116 times.
116 if (ret < 0) {
515 av_log(avf, AV_LOG_ERROR, "Line %d: invalid duration '%s'\n",
516 line, arg_kw[arg]);
517 goto fail;
518 }
519 116 break;
520 2 case 'i': /* integer */
521 2 arg_int[arg] = strtol(get_keyword(&cursor), NULL, 0);
522 2 break;
523 16 case 'k': /* keyword */
524 16 arg_kw[arg] = get_keyword(&cursor);
525 16 break;
526 74 case 's': /* string */
527
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
74 av_assert0(!arg_str[arg]);
528 74 arg_str[arg] = av_get_token((const char **)&cursor, SPACE_CHARS);
529
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
74 if (!arg_str[arg])
530 FAIL(AVERROR(ENOMEM));
531
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
74 if (!*arg_str[arg]) {
532 av_log(avf, AV_LOG_ERROR, "Line %d: string required\n", line);
533 FAIL(AVERROR_INVALIDDATA);
534 }
535 74 break;
536 default:
537 FAIL(AVERROR_BUG);
538 }
539 }
540
541 /* Directive action */
542
9/15
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 58 times.
✓ Branch 4 taken 55 times.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 2 times.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
199 switch ((ParseDirective)(dir - syntax)) {
543
544 5 case DIR_FFCONCAT:
545
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if (strcmp(arg_kw[0], "version") || strcmp(arg_kw[1], "1.0")) {
546 av_log(avf, AV_LOG_ERROR, "Line %d: invalid version\n", line);
547 FAIL(AVERROR_INVALIDDATA);
548 }
549 199 break;
550
551 68 case DIR_FILE:
552 68 ret = add_file(avf, arg_str[0], &file, &nb_files_alloc);
553 68 arg_str[0] = NULL;
554
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (ret < 0)
555 goto fail;
556 68 break;
557
558 3 case DIR_DURATION:
559 3 file->user_duration = arg_int[0];
560 3 break;
561
562 58 case DIR_INPOINT:
563 58 file->inpoint = arg_int[0];
564 58 break;
565
566 55 case DIR_OUTPOINT:
567 55 file->outpoint = arg_int[0];
568 55 break;
569
570 4 case DIR_FPMETA:
571 4 ret = av_dict_set(&file->metadata, arg_kw[0], arg_str[1], AV_DICT_DONT_STRDUP_VAL);
572 4 arg_str[1] = NULL;
573
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
574 FAIL(ret);
575 4 break;
576
577 case DIR_FPMETAS:
578 if ((ret = av_dict_parse_string(&file->metadata, arg_str[0], "=", "", 0)) < 0) {
579 av_log(avf, AV_LOG_ERROR, "Line %d: failed to parse metadata string\n", line);
580 FAIL(AVERROR_INVALIDDATA);
581 }
582 av_log(avf, AV_LOG_WARNING,
583 "'file_packet_metadata key=value:key=value' is deprecated, "
584 "use multiple 'file_packet_meta key value' instead\n");
585 av_freep(&arg_str[0]);
586 break;
587
588 case DIR_OPTION:
589 ret = av_dict_set(&file->options, arg_kw[0], arg_str[1], AV_DICT_DONT_STRDUP_VAL);
590 arg_str[1] = NULL;
591 if (ret < 0)
592 FAIL(ret);
593 break;
594
595 2 case DIR_STREAM:
596 2 stream = avformat_new_stream(avf, NULL);
597
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!stream)
598 FAIL(AVERROR(ENOMEM));
599 2 break;
600
601 2 case DIR_EXSID:
602 2 stream->id = arg_int[0];
603 2 break;
604 2 case DIR_STMETA:
605 2 ret = av_dict_set(&stream->metadata, arg_kw[0], arg_str[1], AV_DICT_DONT_STRDUP_VAL);
606 2 arg_str[1] = NULL;
607
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0)
608 FAIL(ret);
609 2 break;
610
611 case DIR_STCODEC: {
612 const AVCodecDescriptor *codec = avcodec_descriptor_get_by_name(arg_kw[0]);
613 if (!codec) {
614 av_log(avf, AV_LOG_ERROR, "Line %d: codec '%s' not found\n", line, arg_kw[0]);
615 FAIL(AVERROR_DECODER_NOT_FOUND);
616 }
617 stream->codecpar->codec_type = codec->type;
618 stream->codecpar->codec_id = codec->id;
619 break;
620 }
621
622 case DIR_STEDATA: {
623 int size = ff_hex_to_data(NULL, arg_kw[0]);
624 ret = ff_alloc_extradata(stream->codecpar, size);
625 if (ret < 0)
626 FAIL(ret);
627 ff_hex_to_data(stream->codecpar->extradata, arg_kw[0]);
628 break;
629 }
630
631 case DIR_CHAPTER:
632 chapter = avpriv_new_chapter(avf, arg_int[0], AV_TIME_BASE_Q,
633 arg_int[1], arg_int[2], NULL);
634 if (!chapter)
635 FAIL(ENOMEM);
636 break;
637
638 default:
639 FAIL(AVERROR_BUG);
640 }
641 }
642
643
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!file) {
644 ret = AVERROR_INVALIDDATA;
645 goto fail;
646 }
647
648
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3 times.
5 if (file->inpoint != AV_NOPTS_VALUE && file->outpoint != AV_NOPTS_VALUE) {
649
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (file->inpoint > file->outpoint ||
650
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 file->outpoint - (uint64_t)file->inpoint > INT64_MAX)
651 ret = AVERROR_INVALIDDATA;
652 }
653
654 5 fail:
655
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 5 times.
20 for (arg = 0; arg < MAX_ARGS; arg++)
656 15 av_freep(&arg_str[arg]);
657 5 av_bprint_finalize(&bp, NULL);
658
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 return ret == AVERROR_EOF ? 0 : ret;
659 }
660
661 5 static int concat_read_header(AVFormatContext *avf)
662 {
663 5 ConcatContext *cat = avf->priv_data;
664 5 int64_t time = 0;
665 unsigned i;
666 int ret;
667
668 5 ret = concat_parse_script(avf);
669
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (ret < 0)
670 return ret;
671
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!cat->nb_files) {
672 av_log(avf, AV_LOG_ERROR, "No files to concat\n");
673 return AVERROR_INVALIDDATA;
674 }
675
676
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 for (i = 0; i < cat->nb_files; i++) {
677
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (cat->files[i].start_time == AV_NOPTS_VALUE)
678 5 cat->files[i].start_time = time;
679 else
680 time = cat->files[i].start_time;
681
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (cat->files[i].user_duration == AV_NOPTS_VALUE) {
682
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 if (cat->files[i].inpoint == AV_NOPTS_VALUE || cat->files[i].outpoint == AV_NOPTS_VALUE ||
683 cat->files[i].outpoint - (uint64_t)cat->files[i].inpoint != av_sat_sub64(cat->files[i].outpoint, cat->files[i].inpoint)
684 )
685 break;
686 cat->files[i].user_duration = cat->files[i].outpoint - cat->files[i].inpoint;
687 }
688 cat->files[i].duration = cat->files[i].user_duration;
689 if (time + (uint64_t)cat->files[i].user_duration > INT64_MAX)
690 return AVERROR_INVALIDDATA;
691 time += cat->files[i].user_duration;
692 }
693
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (i == cat->nb_files) {
694 avf->duration = time;
695 cat->seekable = 1;
696 }
697
698 5 cat->stream_match_mode = avf->nb_streams ? MATCH_EXACT_ID :
699 MATCH_ONE_TO_ONE;
700
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if ((ret = open_file(avf, 0)) < 0)
701 return ret;
702
703 5 return 0;
704 }
705
706 68 static int open_next_file(AVFormatContext *avf)
707 {
708 68 ConcatContext *cat = avf->priv_data;
709 68 unsigned fileno = cat->cur_file - cat->files;
710
711 68 cat->cur_file->duration = get_best_effort_duration(cat->cur_file, cat->avf);
712
713
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 63 times.
68 if (++fileno >= cat->nb_files) {
714 5 cat->eof = 1;
715 5 return AVERROR_EOF;
716 }
717 63 return open_file(avf, fileno);
718 }
719
720 1081 static int filter_packet(AVFormatContext *avf, ConcatStream *cs, AVPacket *pkt)
721 {
722 int ret;
723
724
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1081 times.
1081 if (cs->bsf) {
725 ret = av_bsf_send_packet(cs->bsf, pkt);
726 if (ret < 0) {
727 av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb filter "
728 "failed to send input packet\n");
729 return ret;
730 }
731
732 while (!ret)
733 ret = av_bsf_receive_packet(cs->bsf, pkt);
734
735 if (ret < 0 && (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)) {
736 av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb filter "
737 "failed to receive output packet\n");
738 return ret;
739 }
740 }
741 1081 return 0;
742 }
743
744 /* Returns true if the packet dts is greater or equal to the specified outpoint. */
745 1134 static int packet_after_outpoint(ConcatContext *cat, AVPacket *pkt)
746 {
747
3/4
✓ Branch 0 taken 570 times.
✓ Branch 1 taken 564 times.
✓ Branch 2 taken 570 times.
✗ Branch 3 not taken.
1134 if (cat->cur_file->outpoint != AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE) {
748 570 return av_compare_ts(pkt->dts, cat->avf->streams[pkt->stream_index]->time_base,
749 570 cat->cur_file->outpoint, AV_TIME_BASE_Q) >= 0;
750 }
751 564 return 0;
752 }
753
754 1086 static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt)
755 {
756 1086 ConcatContext *cat = avf->priv_data;
757 int ret;
758 int64_t delta;
759 ConcatStream *cs;
760 AVStream *st;
761 FFStream *sti;
762
763
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1086 times.
1086 if (cat->eof)
764 return AVERROR_EOF;
765
766
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1086 times.
1086 if (!cat->avf)
767 return AVERROR(EIO);
768
769 while (1) {
770 1149 ret = av_read_frame(cat->avf, pkt);
771
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1134 times.
1149 if (ret == AVERROR_EOF) {
772
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 13 times.
15 if ((ret = open_next_file(avf)) < 0)
773 2 return ret;
774 13 continue;
775 }
776
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1134 times.
1134 if (ret < 0)
777 return ret;
778
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1134 times.
1134 if ((ret = match_streams(avf)) < 0) {
779 return ret;
780 }
781
2/2
✓ Branch 1 taken 53 times.
✓ Branch 2 taken 1081 times.
1134 if (packet_after_outpoint(cat, pkt)) {
782 53 av_packet_unref(pkt);
783
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 50 times.
53 if ((ret = open_next_file(avf)) < 0)
784 3 return ret;
785 50 continue;
786 }
787 1081 cs = &cat->cur_file->streams[pkt->stream_index];
788
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1081 times.
1081 if (cs->out_stream_index < 0) {
789 av_packet_unref(pkt);
790 continue;
791 }
792 1081 break;
793 }
794
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1081 times.
1081 if ((ret = filter_packet(avf, cs, pkt)) < 0)
795 return ret;
796
797 1081 st = cat->avf->streams[pkt->stream_index];
798 1081 sti = ffstream(st);
799 5405 av_log(avf, AV_LOG_DEBUG, "file:%d stream:%d pts:%s pts_time:%s dts:%s dts_time:%s",
800 1081 (unsigned)(cat->cur_file - cat->files), pkt->stream_index,
801 1081 av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),
802 1081 av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));
803
804 1081 delta = av_rescale_q(cat->cur_file->start_time - cat->cur_file->file_inpoint,
805 1081 AV_TIME_BASE_Q,
806 1081 cat->avf->streams[pkt->stream_index]->time_base);
807
1/2
✓ Branch 0 taken 1081 times.
✗ Branch 1 not taken.
1081 if (pkt->pts != AV_NOPTS_VALUE)
808 1081 pkt->pts += delta;
809
1/2
✓ Branch 0 taken 1081 times.
✗ Branch 1 not taken.
1081 if (pkt->dts != AV_NOPTS_VALUE)
810 1081 pkt->dts += delta;
811 1081 av_log(avf, AV_LOG_DEBUG, " -> pts:%s pts_time:%s dts:%s dts_time:%s\n",
812 1081 av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),
813 1081 av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));
814
2/2
✓ Branch 0 taken 131 times.
✓ Branch 1 taken 950 times.
1081 if (cat->cur_file->metadata) {
815 size_t metadata_len;
816 131 char* packed_metadata = av_packet_pack_dictionary(cat->cur_file->metadata, &metadata_len);
817
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 131 times.
131 if (!packed_metadata)
818 return AVERROR(ENOMEM);
819 131 ret = av_packet_add_side_data(pkt, AV_PKT_DATA_STRINGS_METADATA,
820 packed_metadata, metadata_len);
821
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 131 times.
131 if (ret < 0) {
822 av_freep(&packed_metadata);
823 return ret;
824 }
825 }
826
827
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1081 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1081 if (cat->cur_file->duration == AV_NOPTS_VALUE && sti->cur_dts != AV_NOPTS_VALUE) {
828 int64_t next_dts = av_rescale_q(sti->cur_dts, st->time_base, AV_TIME_BASE_Q);
829 if (cat->cur_file->next_dts == AV_NOPTS_VALUE || next_dts > cat->cur_file->next_dts) {
830 cat->cur_file->next_dts = next_dts;
831 }
832 }
833
834 1081 pkt->stream_index = cs->out_stream_index;
835 1081 return 0;
836 }
837
838 static int try_seek(AVFormatContext *avf, int stream,
839 int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
840 {
841 ConcatContext *cat = avf->priv_data;
842 int64_t t0 = cat->cur_file->start_time - cat->cur_file->file_inpoint;
843
844 ts -= t0;
845 min_ts = min_ts == INT64_MIN ? INT64_MIN : min_ts - t0;
846 max_ts = max_ts == INT64_MAX ? INT64_MAX : max_ts - t0;
847 if (stream >= 0) {
848 if (stream >= cat->avf->nb_streams)
849 return AVERROR(EIO);
850 ff_rescale_interval(AV_TIME_BASE_Q, cat->avf->streams[stream]->time_base,
851 &min_ts, &ts, &max_ts);
852 }
853 return avformat_seek_file(cat->avf, stream, min_ts, ts, max_ts, flags);
854 }
855
856 static int real_seek(AVFormatContext *avf, int stream,
857 int64_t min_ts, int64_t ts, int64_t max_ts, int flags, AVFormatContext *cur_avf)
858 {
859 ConcatContext *cat = avf->priv_data;
860 int ret, left, right;
861
862 if (stream >= 0) {
863 if (stream >= avf->nb_streams)
864 return AVERROR(EINVAL);
865 ff_rescale_interval(avf->streams[stream]->time_base, AV_TIME_BASE_Q,
866 &min_ts, &ts, &max_ts);
867 }
868
869 left = 0;
870 right = cat->nb_files;
871
872 /* Always support seek to start */
873 if (ts <= 0)
874 right = 1;
875 else if (!cat->seekable)
876 return AVERROR(ESPIPE); /* XXX: can we use it? */
877
878 while (right - left > 1) {
879 int mid = (left + right) / 2;
880 if (ts < cat->files[mid].start_time)
881 right = mid;
882 else
883 left = mid;
884 }
885
886 if (cat->cur_file != &cat->files[left]) {
887 if ((ret = open_file(avf, left)) < 0)
888 return ret;
889 } else {
890 cat->avf = cur_avf;
891 }
892
893 ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
894 if (ret < 0 &&
895 left < cat->nb_files - 1 &&
896 cat->files[left + 1].start_time < max_ts) {
897 if (cat->cur_file == &cat->files[left])
898 cat->avf = NULL;
899 if ((ret = open_file(avf, left + 1)) < 0)
900 return ret;
901 ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
902 }
903 return ret;
904 }
905
906 static int concat_seek(AVFormatContext *avf, int stream,
907 int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
908 {
909 ConcatContext *cat = avf->priv_data;
910 ConcatFile *cur_file_saved = cat->cur_file;
911 AVFormatContext *cur_avf_saved = cat->avf;
912 int ret;
913
914 if (flags & (AVSEEK_FLAG_BYTE | AVSEEK_FLAG_FRAME))
915 return AVERROR(ENOSYS);
916 cat->avf = NULL;
917 if ((ret = real_seek(avf, stream, min_ts, ts, max_ts, flags, cur_avf_saved)) < 0) {
918 if (cat->cur_file != cur_file_saved) {
919 if (cat->avf)
920 avformat_close_input(&cat->avf);
921 }
922 cat->avf = cur_avf_saved;
923 cat->cur_file = cur_file_saved;
924 } else {
925 if (cat->cur_file != cur_file_saved) {
926 avformat_close_input(&cur_avf_saved);
927 }
928 cat->eof = 0;
929 }
930 return ret;
931 }
932
933 #define OFFSET(x) offsetof(ConcatContext, x)
934 #define DEC AV_OPT_FLAG_DECODING_PARAM
935
936 static const AVOption options[] = {
937 { "safe", "enable safe mode",
938 OFFSET(safe), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DEC },
939 { "auto_convert", "automatically convert bitstream format",
940 OFFSET(auto_convert), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DEC },
941 { "segment_time_metadata", "output file segment start time and duration as packet metadata",
942 OFFSET(segment_time_metadata), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
943 { NULL }
944 };
945
946 static const AVClass concat_class = {
947 .class_name = "concat demuxer",
948 .item_name = av_default_item_name,
949 .option = options,
950 .version = LIBAVUTIL_VERSION_INT,
951 };
952
953
954 const FFInputFormat ff_concat_demuxer = {
955 .p.name = "concat",
956 .p.long_name = NULL_IF_CONFIG_SMALL("Virtual concatenation script"),
957 .p.priv_class = &concat_class,
958 .priv_data_size = sizeof(ConcatContext),
959 .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,
960 .read_probe = concat_probe,
961 .read_header = concat_read_header,
962 .read_packet = concat_read_packet,
963 .read_close = concat_read_close,
964 .read_seek2 = concat_seek,
965 };
966