FFmpeg coverage


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