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/avassert.h"
22 #include "libavutil/avstring.h"
23 #include "libavutil/intreadwrite.h"
24 #include "libavutil/opt.h"
25 #include "libavutil/parseutils.h"
26 #include "avformat.h"
27 #include "internal.h"
28 #include "url.h"
29 
30 #ifndef SIZE_MAX
31 #  ifdef __SIZE_MAX__
32 #    define SIZE_MAX __SIZE_MAX__
33 #  else
34 #    error NO SIZE_MAX
35 #  endif
36 #endif
37 
38 typedef enum ConcatMatchMode {
39     MATCH_ONE_TO_ONE,
40     MATCH_EXACT_ID,
41 } ConcatMatchMode;
42 
43 typedef struct ConcatStream {
44     AVBitStreamFilterContext *bsf;
45     int out_stream_index;
46 } ConcatStream;
47 
48 typedef struct {
49     char *url;
50     int64_t start_time;
51     int64_t duration;
52     ConcatStream *streams;
53     int nb_streams;
54 } ConcatFile;
55 
56 typedef struct {
57     AVClass *class;
58     ConcatFile *files;
59     ConcatFile *cur_file;
60     unsigned nb_files;
61     AVFormatContext *avf;
62     int safe;
63     int seekable;
64     ConcatMatchMode stream_match_mode;
65     unsigned auto_convert;
66 } ConcatContext;
67 
concat_probe(AVProbeData * probe)68 static int concat_probe(AVProbeData *probe)
69 {
70     return memcmp(probe->buf, "ffconcat version 1.0", 20) ?
71            0 : AVPROBE_SCORE_MAX;
72 }
73 
get_keyword(uint8_t ** cursor)74 static char *get_keyword(uint8_t **cursor)
75 {
76     char *ret = *cursor += strspn(*cursor, SPACE_CHARS);
77     *cursor += strcspn(*cursor, SPACE_CHARS);
78     if (**cursor) {
79         *((*cursor)++) = 0;
80         *cursor += strspn(*cursor, SPACE_CHARS);
81     }
82     return ret;
83 }
84 
safe_filename(const char * f)85 static int safe_filename(const char *f)
86 {
87     const char *start = f;
88 
89     for (; *f; f++) {
90         /* A-Za-z0-9_- */
91         if (!((unsigned)((*f | 32) - 'a') < 26 ||
92               (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) {
93             if (f == start)
94                 return 0;
95             else if (*f == '/')
96                 start = f + 1;
97             else if (*f != '.')
98                 return 0;
99         }
100     }
101     return 1;
102 }
103 
104 #define FAIL(retcode) do { ret = (retcode); goto fail; } while(0)
105 
add_file(AVFormatContext * avf,char * filename,ConcatFile ** rfile,unsigned * nb_files_alloc)106 static int add_file(AVFormatContext *avf, char *filename, ConcatFile **rfile,
107                     unsigned *nb_files_alloc)
108 {
109     ConcatContext *cat = avf->priv_data;
110     ConcatFile *file;
111     char *url = NULL;
112     const char *proto;
113     size_t url_len, proto_len;
114     int ret;
115 
116     if (cat->safe > 0 && !safe_filename(filename)) {
117         av_log(avf, AV_LOG_ERROR, "Unsafe file name '%s'\n", filename);
118         FAIL(AVERROR(EPERM));
119     }
120 
121     proto = avio_find_protocol_name(filename);
122     proto_len = proto ? strlen(proto) : 0;
123     if (!memcmp(filename, proto, proto_len) &&
124         (filename[proto_len] == ':' || filename[proto_len] == ',')) {
125         url = filename;
126         filename = NULL;
127     } else {
128         url_len = strlen(avf->filename) + strlen(filename) + 16;
129         if (!(url = av_malloc(url_len)))
130             FAIL(AVERROR(ENOMEM));
131         ff_make_absolute_url(url, url_len, avf->filename, filename);
132         av_freep(&filename);
133     }
134 
135     if (cat->nb_files >= *nb_files_alloc) {
136         size_t n = FFMAX(*nb_files_alloc * 2, 16);
137         ConcatFile *new_files;
138         if (n <= cat->nb_files || n > SIZE_MAX / sizeof(*cat->files) ||
139             !(new_files = av_realloc(cat->files, n * sizeof(*cat->files))))
140             FAIL(AVERROR(ENOMEM));
141         cat->files = new_files;
142         *nb_files_alloc = n;
143     }
144 
145     file = &cat->files[cat->nb_files++];
146     memset(file, 0, sizeof(*file));
147     *rfile = file;
148 
149     file->url        = url;
150     file->start_time = AV_NOPTS_VALUE;
151     file->duration   = AV_NOPTS_VALUE;
152 
153     return 0;
154 
155 fail:
156     av_free(url);
157     av_free(filename);
158     return ret;
159 }
160 
copy_stream_props(AVStream * st,AVStream * source_st)161 static int copy_stream_props(AVStream *st, AVStream *source_st)
162 {
163     int ret;
164 
165     if (st->codec->codec_id || !source_st->codec->codec_id) {
166         if (st->codec->extradata_size < source_st->codec->extradata_size) {
167             ret = ff_alloc_extradata(st->codec,
168                                      source_st->codec->extradata_size);
169             if (ret < 0)
170                 return ret;
171         }
172         memcpy(st->codec->extradata, source_st->codec->extradata,
173                source_st->codec->extradata_size);
174         return 0;
175     }
176     if ((ret = avcodec_copy_context(st->codec, source_st->codec)) < 0)
177         return ret;
178     st->r_frame_rate        = source_st->r_frame_rate;
179     st->avg_frame_rate      = source_st->avg_frame_rate;
180     st->time_base           = source_st->time_base;
181     st->sample_aspect_ratio = source_st->sample_aspect_ratio;
182     return 0;
183 }
184 
detect_stream_specific(AVFormatContext * avf,int idx)185 static int detect_stream_specific(AVFormatContext *avf, int idx)
186 {
187     ConcatContext *cat = avf->priv_data;
188     AVStream *st = cat->avf->streams[idx];
189     ConcatStream *cs = &cat->cur_file->streams[idx];
190     AVBitStreamFilterContext *bsf;
191 
192     if (cat->auto_convert && st->codec->codec_id == AV_CODEC_ID_H264 &&
193         (st->codec->extradata_size < 4 || AV_RB32(st->codec->extradata) != 1)) {
194         av_log(cat->avf, AV_LOG_INFO,
195                "Auto-inserting h264_mp4toannexb bitstream filter\n");
196         if (!(bsf = av_bitstream_filter_init("h264_mp4toannexb"))) {
197             av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb bitstream filter "
198                    "required for H.264 streams\n");
199             return AVERROR_BSF_NOT_FOUND;
200         }
201         cs->bsf = bsf;
202     }
203     return 0;
204 }
205 
match_streams_one_to_one(AVFormatContext * avf)206 static int match_streams_one_to_one(AVFormatContext *avf)
207 {
208     ConcatContext *cat = avf->priv_data;
209     AVStream *st;
210     int i, ret;
211 
212     for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
213         if (i < avf->nb_streams) {
214             st = avf->streams[i];
215         } else {
216             if (!(st = avformat_new_stream(avf, NULL)))
217                 return AVERROR(ENOMEM);
218         }
219         if ((ret = copy_stream_props(st, cat->avf->streams[i])) < 0)
220             return ret;
221         cat->cur_file->streams[i].out_stream_index = i;
222     }
223     return 0;
224 }
225 
match_streams_exact_id(AVFormatContext * avf)226 static int match_streams_exact_id(AVFormatContext *avf)
227 {
228     ConcatContext *cat = avf->priv_data;
229     AVStream *st;
230     int i, j, ret;
231 
232     for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
233         st = cat->avf->streams[i];
234         for (j = 0; j < avf->nb_streams; j++) {
235             if (avf->streams[j]->id == st->id) {
236                 av_log(avf, AV_LOG_VERBOSE,
237                        "Match slave stream #%d with stream #%d id 0x%x\n",
238                        i, j, st->id);
239                 if ((ret = copy_stream_props(avf->streams[j], st)) < 0)
240                     return ret;
241                 cat->cur_file->streams[i].out_stream_index = j;
242             }
243         }
244     }
245     return 0;
246 }
247 
match_streams(AVFormatContext * avf)248 static int match_streams(AVFormatContext *avf)
249 {
250     ConcatContext *cat = avf->priv_data;
251     ConcatStream *map;
252     int i, ret;
253 
254     if (cat->cur_file->nb_streams >= cat->avf->nb_streams)
255         return 0;
256     map = av_realloc(cat->cur_file->streams,
257                      cat->avf->nb_streams * sizeof(*map));
258     if (!map)
259         return AVERROR(ENOMEM);
260     cat->cur_file->streams = map;
261     memset(map + cat->cur_file->nb_streams, 0,
262            (cat->avf->nb_streams - cat->cur_file->nb_streams) * sizeof(*map));
263 
264     for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++)
265         map[i].out_stream_index = -1;
266     switch (cat->stream_match_mode) {
267     case MATCH_ONE_TO_ONE:
268         ret = match_streams_one_to_one(avf);
269         break;
270     case MATCH_EXACT_ID:
271         ret = match_streams_exact_id(avf);
272         break;
273     default:
274         ret = AVERROR_BUG;
275     }
276     if (ret < 0)
277         return ret;
278     for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++)
279         if ((ret = detect_stream_specific(avf, i)) < 0)
280             return ret;
281     cat->cur_file->nb_streams = cat->avf->nb_streams;
282     return 0;
283 }
284 
open_file(AVFormatContext * avf,unsigned fileno)285 static int open_file(AVFormatContext *avf, unsigned fileno)
286 {
287     ConcatContext *cat = avf->priv_data;
288     ConcatFile *file = &cat->files[fileno];
289     int ret;
290 
291     if (cat->avf)
292         avformat_close_input(&cat->avf);
293 
294     cat->avf = avformat_alloc_context();
295     if (!cat->avf)
296         return AVERROR(ENOMEM);
297 
298     cat->avf->interrupt_callback = avf->interrupt_callback;
299     if ((ret = avformat_open_input(&cat->avf, file->url, NULL, NULL)) < 0 ||
300         (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) {
301         av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url);
302         avformat_close_input(&cat->avf);
303         return ret;
304     }
305     cat->cur_file = file;
306     if (file->start_time == AV_NOPTS_VALUE)
307         file->start_time = !fileno ? 0 :
308                            cat->files[fileno - 1].start_time +
309                            cat->files[fileno - 1].duration;
310     if ((ret = match_streams(avf)) < 0)
311         return ret;
312     return 0;
313 }
314 
concat_read_close(AVFormatContext * avf)315 static int concat_read_close(AVFormatContext *avf)
316 {
317     ConcatContext *cat = avf->priv_data;
318     unsigned i;
319 
320     if (cat->avf)
321         avformat_close_input(&cat->avf);
322     for (i = 0; i < cat->nb_files; i++) {
323         av_freep(&cat->files[i].url);
324         av_freep(&cat->files[i].streams);
325     }
326     av_freep(&cat->files);
327     return 0;
328 }
329 
330 /* type pun fix */
331 typedef union {
332     uint8_t *t_uint8_t;
333     const char *t_const_char;
334 } u_uint8_cchar;
335 
concat_read_header(AVFormatContext * avf)336 static int concat_read_header(AVFormatContext *avf)
337 {
338     ConcatContext *cat = avf->priv_data;
339     uint8_t buf[4096];
340     uint8_t *keyword;
341     u_uint8_cchar cursor;
342     int ret, line = 0, i;
343     unsigned nb_files_alloc = 0;
344     ConcatFile *file = NULL;
345     int64_t time = 0;
346 
347     while (1) {
348         if ((ret = ff_get_line(avf->pb, buf, sizeof(buf))) <= 0)
349             break;
350         line++;
351         cursor.t_uint8_t = buf;
352         keyword = get_keyword(&cursor.t_uint8_t);
353         if (!*keyword || *keyword == '#')
354             continue;
355 
356         if (!strcmp(keyword, "file")) {
357             char *filename = av_get_token((const char **)&cursor, SPACE_CHARS);
358             if (!filename) {
359                 av_log(avf, AV_LOG_ERROR, "Line %d: filename required\n", line);
360                 FAIL(AVERROR_INVALIDDATA);
361             }
362             if ((ret = add_file(avf, filename, &file, &nb_files_alloc)) < 0)
363                 goto fail;
364         } else if (!strcmp(keyword, "duration")) {
365             char *dur_str = get_keyword(&cursor.t_uint8_t);
366             int64_t dur;
367             if (!file) {
368                 av_log(avf, AV_LOG_ERROR, "Line %d: duration without file\n",
369                        line);
370                 FAIL(AVERROR_INVALIDDATA);
371             }
372             if ((ret = av_parse_time(&dur, dur_str, 1)) < 0) {
373                 av_log(avf, AV_LOG_ERROR, "Line %d: invalid duration '%s'\n",
374                        line, dur_str);
375                 goto fail;
376             }
377             file->duration = dur;
378         } else if (!strcmp(keyword, "stream")) {
379             if (!avformat_new_stream(avf, NULL))
380                 FAIL(AVERROR(ENOMEM));
381         } else if (!strcmp(keyword, "exact_stream_id")) {
382             if (!avf->nb_streams) {
383                 av_log(avf, AV_LOG_ERROR, "Line %d: exact_stream_id without stream\n",
384                        line);
385                 FAIL(AVERROR_INVALIDDATA);
386             }
387             avf->streams[avf->nb_streams - 1]->id =
388                 strtol(get_keyword(&cursor.t_uint8_t), NULL, 0);
389         } else if (!strcmp(keyword, "ffconcat")) {
390             char *ver_kw  = get_keyword(&cursor.t_uint8_t);
391             char *ver_val = get_keyword(&cursor.t_uint8_t);
392             if (strcmp(ver_kw, "version") || strcmp(ver_val, "1.0")) {
393                 av_log(avf, AV_LOG_ERROR, "Line %d: invalid version\n", line);
394                 FAIL(AVERROR_INVALIDDATA);
395             }
396             if (cat->safe < 0)
397                 cat->safe = 1;
398         } else {
399             av_log(avf, AV_LOG_ERROR, "Line %d: unknown keyword '%s'\n",
400                    line, keyword);
401             FAIL(AVERROR_INVALIDDATA);
402         }
403     }
404     if (ret < 0)
405         goto fail;
406     if (!cat->nb_files)
407         FAIL(AVERROR_INVALIDDATA);
408 
409     for (i = 0; i < cat->nb_files; i++) {
410         if (cat->files[i].start_time == AV_NOPTS_VALUE)
411             cat->files[i].start_time = time;
412         else
413             time = cat->files[i].start_time;
414         if (cat->files[i].duration == AV_NOPTS_VALUE)
415             break;
416         time += cat->files[i].duration;
417     }
418     if (i == cat->nb_files) {
419         avf->duration = time;
420         cat->seekable = 1;
421     }
422 
423     cat->stream_match_mode = avf->nb_streams ? MATCH_EXACT_ID :
424                                                MATCH_ONE_TO_ONE;
425     if ((ret = open_file(avf, 0)) < 0)
426         goto fail;
427     return 0;
428 
429 fail:
430     concat_read_close(avf);
431     return ret;
432 }
433 
open_next_file(AVFormatContext * avf)434 static int open_next_file(AVFormatContext *avf)
435 {
436     ConcatContext *cat = avf->priv_data;
437     unsigned fileno = cat->cur_file - cat->files;
438 
439     if (cat->cur_file->duration == AV_NOPTS_VALUE)
440         cat->cur_file->duration = cat->avf->duration;
441 
442     if (++fileno >= cat->nb_files)
443         return AVERROR_EOF;
444     return open_file(avf, fileno);
445 }
446 
filter_packet(AVFormatContext * avf,ConcatStream * cs,AVPacket * pkt)447 static int filter_packet(AVFormatContext *avf, ConcatStream *cs, AVPacket *pkt)
448 {
449     AVStream *st = avf->streams[cs->out_stream_index];
450     AVBitStreamFilterContext *bsf;
451     AVPacket pkt2;
452     int ret;
453 
454     av_assert0(cs->out_stream_index >= 0);
455     for (bsf = cs->bsf; bsf; bsf = bsf->next) {
456         pkt2 = *pkt;
457         ret = av_bitstream_filter_filter(bsf, st->codec, NULL,
458                                          &pkt2.data, &pkt2.size,
459                                          pkt->data, pkt->size,
460                                          !!(pkt->flags & AV_PKT_FLAG_KEY));
461         if (ret < 0) {
462             av_packet_unref(pkt);
463             return ret;
464         }
465         av_assert0(pkt2.buf);
466         if (ret == 0 && pkt2.data != pkt->data) {
467             if ((ret = av_copy_packet(&pkt2, pkt)) < 0) {
468                 av_free(pkt2.data);
469                 return ret;
470             }
471             ret = 1;
472         }
473         if (ret > 0) {
474             av_free_packet(pkt);
475             pkt2.buf = av_buffer_create(pkt2.data, pkt2.size,
476                                         av_buffer_default_free, NULL, 0);
477             if (!pkt2.buf) {
478                 av_free(pkt2.data);
479                 return AVERROR(ENOMEM);
480             }
481         }
482         *pkt = pkt2;
483     }
484     return 0;
485 }
486 
concat_read_packet(AVFormatContext * avf,AVPacket * pkt)487 static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt)
488 {
489     ConcatContext *cat = avf->priv_data;
490     int ret;
491     int64_t delta;
492     ConcatStream *cs;
493 
494     while (1) {
495         ret = av_read_frame(cat->avf, pkt);
496         if (ret == AVERROR_EOF) {
497             if ((ret = open_next_file(avf)) < 0)
498                 return ret;
499             continue;
500         }
501         if (ret < 0)
502             return ret;
503         if ((ret = match_streams(avf)) < 0) {
504             av_packet_unref(pkt);
505             return ret;
506         }
507         cs = &cat->cur_file->streams[pkt->stream_index];
508         if (cs->out_stream_index < 0) {
509             av_packet_unref(pkt);
510             continue;
511         }
512         pkt->stream_index = cs->out_stream_index;
513         break;
514     }
515     if ((ret = filter_packet(avf, cs, pkt)))
516         return ret;
517 
518 	delta = av_rescale_q(cat->cur_file->start_time - cat->avf->start_time,
519                          AV_TIME_BASE_Q,
520                          cat->avf->streams[pkt->stream_index]->time_base);
521 	if (pkt->pts != AV_NOPTS_VALUE)
522         pkt->pts += delta;
523     if (pkt->dts != AV_NOPTS_VALUE)
524         pkt->dts += delta;
525     return ret;
526 }
527 
rescale_interval(AVRational tb_in,AVRational tb_out,int64_t * min_ts,int64_t * ts,int64_t * max_ts)528 static void rescale_interval(AVRational tb_in, AVRational tb_out,
529                              int64_t *min_ts, int64_t *ts, int64_t *max_ts)
530 {
531     *ts     = av_rescale_q    (*    ts, tb_in, tb_out);
532     *min_ts = av_rescale_q_rnd(*min_ts, tb_in, tb_out,
533                                AV_ROUND_UP   | AV_ROUND_PASS_MINMAX);
534     *max_ts = av_rescale_q_rnd(*max_ts, tb_in, tb_out,
535                                AV_ROUND_DOWN | AV_ROUND_PASS_MINMAX);
536 }
537 
try_seek(AVFormatContext * avf,int stream,int64_t min_ts,int64_t ts,int64_t max_ts,int flags)538 static int try_seek(AVFormatContext *avf, int stream,
539                     int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
540 {
541     ConcatContext *cat = avf->priv_data;
542     int64_t t0 = cat->cur_file->start_time - cat->avf->start_time;
543 
544     ts -= t0;
545     min_ts = min_ts == INT64_MIN ? INT64_MIN : min_ts - t0;
546     max_ts = max_ts == INT64_MAX ? INT64_MAX : max_ts - t0;
547     if (stream >= 0) {
548         if (stream >= cat->avf->nb_streams)
549             return AVERROR(EIO);
550 		rescale_interval(AV_TIME_BASE_Q, cat->avf->streams[stream]->time_base,
551                          &min_ts, &ts, &max_ts);
552 	}
553     return avformat_seek_file(cat->avf, stream, min_ts, ts, max_ts, flags);
554 }
555 
real_seek(AVFormatContext * avf,int stream,int64_t min_ts,int64_t ts,int64_t max_ts,int flags)556 static int real_seek(AVFormatContext *avf, int stream,
557                      int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
558 {
559     ConcatContext *cat = avf->priv_data;
560     int ret, left, right;
561 
562     if (stream >= 0) {
563         if (stream >= avf->nb_streams)
564             return AVERROR(EINVAL);
565 		rescale_interval(avf->streams[stream]->time_base, AV_TIME_BASE_Q,
566                          &min_ts, &ts, &max_ts);
567 	}
568 
569     left  = 0;
570     right = cat->nb_files;
571     while (right - left > 1) {
572         int mid = (left + right) / 2;
573         if (ts < cat->files[mid].start_time)
574             right = mid;
575         else
576             left  = mid;
577     }
578 
579     if ((ret = open_file(avf, left)) < 0)
580         return ret;
581 
582     ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
583     if (ret < 0 &&
584         left < cat->nb_files - 1 &&
585         cat->files[left + 1].start_time < max_ts) {
586         if ((ret = open_file(avf, left + 1)) < 0)
587             return ret;
588         ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
589     }
590     return ret;
591 }
592 
concat_seek(AVFormatContext * avf,int stream,int64_t min_ts,int64_t ts,int64_t max_ts,int flags)593 static int concat_seek(AVFormatContext *avf, int stream,
594                        int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
595 {
596     ConcatContext *cat = avf->priv_data;
597     ConcatFile *cur_file_saved = cat->cur_file;
598     AVFormatContext *cur_avf_saved = cat->avf;
599     int ret;
600 
601     if (!cat->seekable)
602         return AVERROR(ESPIPE); /* XXX: can we use it? */
603     if (flags & (AVSEEK_FLAG_BYTE | AVSEEK_FLAG_FRAME))
604         return AVERROR(ENOSYS);
605     cat->avf = NULL;
606     if ((ret = real_seek(avf, stream, min_ts, ts, max_ts, flags)) < 0) {
607         if (cat->avf)
608             avformat_close_input(&cat->avf);
609         cat->avf      = cur_avf_saved;
610         cat->cur_file = cur_file_saved;
611     } else {
612         avformat_close_input(&cur_avf_saved);
613     }
614     return ret;
615 }
616 
617 #define OFFSET(x) offsetof(ConcatContext, x)
618 #define DEC AV_OPT_FLAG_DECODING_PARAM
619 
620 static const AVOption options[] = {
621 	{ "safe", "enable safe mode",
622       OFFSET(safe), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, DEC },
623     { "auto_convert", "automatically convert bitstream format",
624       OFFSET(auto_convert), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC },
625 	{ NULL }
626 };
627 
628 static const AVClass concat_class = {
629 	.class_name = "concat demuxer",
630     .item_name  = av_default_item_name,
631     .option     = options,
632     .version    = LIBAVUTIL_VERSION_INT,
633 };
634 
635 
636 AVInputFormat ff_concat_demuxer = {
637 	.name           = "concat",
638     .long_name      = NULL_IF_CONFIG_SMALL("Virtual concatenation script"),
639     .priv_data_size = sizeof(ConcatContext),
640     .read_probe     = concat_probe,
641     .read_header    = concat_read_header,
642     .read_packet    = concat_read_packet,
643     .read_close     = concat_read_close,
644     .read_seek2     = concat_seek,
645     .priv_class     = &concat_class,
646 };
647