2 * Copyright (c) 2012 Nicolas George
4 * This file is part of FFmpeg.
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.
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.
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
21 #include "libavutil/avassert.h"
22 #include "libavutil/avstring.h"
23 #include "libavutil/bprint.h"
24 #include "libavutil/intreadwrite.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/parseutils.h"
27 #include "libavutil/timestamp.h"
32 typedef enum ConcatMatchMode
{
37 typedef struct ConcatStream
{
45 int64_t file_start_time
;
48 int64_t user_duration
;
50 ConcatStream
*streams
;
53 AVDictionary
*metadata
;
66 ConcatMatchMode stream_match_mode
;
67 unsigned auto_convert
;
68 int segment_time_metadata
;
71 static int concat_probe(const AVProbeData
*probe
)
73 return memcmp(probe
->buf
, "ffconcat version 1.0", 20) ?
74 0 : AVPROBE_SCORE_MAX
;
77 static char *get_keyword(uint8_t **cursor
)
79 char *ret
= *cursor
+= strspn(*cursor
, SPACE_CHARS
);
80 *cursor
+= strcspn(*cursor
, SPACE_CHARS
);
83 *cursor
+= strspn(*cursor
, SPACE_CHARS
);
88 static int safe_filename(const char *f
)
90 const char *start
= f
;
94 if (!((unsigned)((*f
| 32) - 'a') < 26 ||
95 (unsigned)(*f
- '0') < 10 || *f
== '_' || *f
== '-')) {
107 #define FAIL(retcode) do { ret = (retcode); goto fail; } while(0)
109 static int add_file(AVFormatContext
*avf
, char *filename
, ConcatFile
**rfile
,
110 unsigned *nb_files_alloc
)
112 ConcatContext
*cat
= avf
->priv_data
;
120 if (cat
->safe
> 0 && !safe_filename(filename
)) {
121 av_log(avf
, AV_LOG_ERROR
, "Unsafe file name '%s'\n", filename
);
122 FAIL(AVERROR(EPERM
));
125 proto
= avio_find_protocol_name(filename
);
126 if (proto
&& av_strstart(filename
, proto
, &ptr
) &&
127 (*ptr
== ':' || *ptr
== ',')) {
131 url_len
= strlen(avf
->url
) + strlen(filename
) + 16;
132 if (!(url
= av_malloc(url_len
)))
133 FAIL(AVERROR(ENOMEM
));
134 ff_make_absolute_url(url
, url_len
, avf
->url
, filename
);
138 if (cat
->nb_files
>= *nb_files_alloc
) {
139 size_t n
= FFMAX(*nb_files_alloc
* 2, 16);
140 ConcatFile
*new_files
;
141 if (n
<= cat
->nb_files
|| n
> SIZE_MAX
/ sizeof(*cat
->files
) ||
142 !(new_files
= av_realloc(cat
->files
, n
* sizeof(*cat
->files
))))
143 FAIL(AVERROR(ENOMEM
));
144 cat
->files
= new_files
;
148 file
= &cat
->files
[cat
->nb_files
++];
149 memset(file
, 0, sizeof(*file
));
153 file
->start_time
= AV_NOPTS_VALUE
;
154 file
->duration
= AV_NOPTS_VALUE
;
155 file
->next_dts
= AV_NOPTS_VALUE
;
156 file
->inpoint
= AV_NOPTS_VALUE
;
157 file
->outpoint
= AV_NOPTS_VALUE
;
158 file
->user_duration
= AV_NOPTS_VALUE
;
168 static int copy_stream_props(AVStream
*st
, AVStream
*source_st
)
172 if (st
->codecpar
->codec_id
|| !source_st
->codecpar
->codec_id
) {
173 if (st
->codecpar
->extradata_size
< source_st
->codecpar
->extradata_size
) {
174 ret
= ff_alloc_extradata(st
->codecpar
,
175 source_st
->codecpar
->extradata_size
);
179 memcpy(st
->codecpar
->extradata
, source_st
->codecpar
->extradata
,
180 source_st
->codecpar
->extradata_size
);
183 if ((ret
= avcodec_parameters_copy(st
->codecpar
, source_st
->codecpar
)) < 0)
185 st
->r_frame_rate
= source_st
->r_frame_rate
;
186 st
->avg_frame_rate
= source_st
->avg_frame_rate
;
187 st
->sample_aspect_ratio
= source_st
->sample_aspect_ratio
;
188 avpriv_set_pts_info(st
, 64, source_st
->time_base
.num
, source_st
->time_base
.den
);
190 av_dict_copy(&st
->metadata
, source_st
->metadata
, 0);
194 static int detect_stream_specific(AVFormatContext
*avf
, int idx
)
196 ConcatContext
*cat
= avf
->priv_data
;
197 AVStream
*st
= cat
->avf
->streams
[idx
];
198 ConcatStream
*cs
= &cat
->cur_file
->streams
[idx
];
199 const AVBitStreamFilter
*filter
;
203 if (cat
->auto_convert
&& st
->codecpar
->codec_id
== AV_CODEC_ID_H264
) {
204 if (!st
->codecpar
->extradata_size
||
205 (st
->codecpar
->extradata_size
>= 3 && AV_RB24(st
->codecpar
->extradata
) == 1) ||
206 (st
->codecpar
->extradata_size
>= 4 && AV_RB32(st
->codecpar
->extradata
) == 1))
208 av_log(cat
->avf
, AV_LOG_INFO
,
209 "Auto-inserting h264_mp4toannexb bitstream filter\n");
210 filter
= av_bsf_get_by_name("h264_mp4toannexb");
212 av_log(avf
, AV_LOG_ERROR
, "h264_mp4toannexb bitstream filter "
213 "required for H.264 streams\n");
214 return AVERROR_BSF_NOT_FOUND
;
216 ret
= av_bsf_alloc(filter
, &bsf
);
221 ret
= avcodec_parameters_copy(bsf
->par_in
, st
->codecpar
);
225 ret
= av_bsf_init(bsf
);
229 ret
= avcodec_parameters_copy(st
->codecpar
, bsf
->par_out
);
236 static int match_streams_one_to_one(AVFormatContext
*avf
)
238 ConcatContext
*cat
= avf
->priv_data
;
242 for (i
= cat
->cur_file
->nb_streams
; i
< cat
->avf
->nb_streams
; i
++) {
243 if (i
< avf
->nb_streams
) {
244 st
= avf
->streams
[i
];
246 if (!(st
= avformat_new_stream(avf
, NULL
)))
247 return AVERROR(ENOMEM
);
249 if ((ret
= copy_stream_props(st
, cat
->avf
->streams
[i
])) < 0)
251 cat
->cur_file
->streams
[i
].out_stream_index
= i
;
256 static int match_streams_exact_id(AVFormatContext
*avf
)
258 ConcatContext
*cat
= avf
->priv_data
;
262 for (i
= cat
->cur_file
->nb_streams
; i
< cat
->avf
->nb_streams
; i
++) {
263 st
= cat
->avf
->streams
[i
];
264 for (j
= 0; j
< avf
->nb_streams
; j
++) {
265 if (avf
->streams
[j
]->id
== st
->id
) {
266 av_log(avf
, AV_LOG_VERBOSE
,
267 "Match slave stream #%d with stream #%d id 0x%x\n",
269 if ((ret
= copy_stream_props(avf
->streams
[j
], st
)) < 0)
271 cat
->cur_file
->streams
[i
].out_stream_index
= j
;
278 static int match_streams(AVFormatContext
*avf
)
280 ConcatContext
*cat
= avf
->priv_data
;
284 if (cat
->cur_file
->nb_streams
>= cat
->avf
->nb_streams
)
286 map
= av_realloc(cat
->cur_file
->streams
,
287 cat
->avf
->nb_streams
* sizeof(*map
));
289 return AVERROR(ENOMEM
);
290 cat
->cur_file
->streams
= map
;
291 memset(map
+ cat
->cur_file
->nb_streams
, 0,
292 (cat
->avf
->nb_streams
- cat
->cur_file
->nb_streams
) * sizeof(*map
));
294 for (i
= cat
->cur_file
->nb_streams
; i
< cat
->avf
->nb_streams
; i
++) {
295 map
[i
].out_stream_index
= -1;
296 if ((ret
= detect_stream_specific(avf
, i
)) < 0)
299 switch (cat
->stream_match_mode
) {
300 case MATCH_ONE_TO_ONE
:
301 ret
= match_streams_one_to_one(avf
);
304 ret
= match_streams_exact_id(avf
);
311 cat
->cur_file
->nb_streams
= cat
->avf
->nb_streams
;
315 static int64_t get_best_effort_duration(ConcatFile
*file
, AVFormatContext
*avf
)
317 if (file
->user_duration
!= AV_NOPTS_VALUE
)
318 return file
->user_duration
;
319 if (file
->outpoint
!= AV_NOPTS_VALUE
)
320 return av_sat_sub64(file
->outpoint
, file
->file_inpoint
);
321 if (avf
->duration
> 0)
322 return avf
->duration
- (file
->file_inpoint
- file
->file_start_time
);
323 if (file
->next_dts
!= AV_NOPTS_VALUE
)
324 return file
->next_dts
- file
->file_inpoint
;
325 return AV_NOPTS_VALUE
;
328 static int open_file(AVFormatContext
*avf
, unsigned fileno
)
330 ConcatContext
*cat
= avf
->priv_data
;
331 ConcatFile
*file
= &cat
->files
[fileno
];
335 avformat_close_input(&cat
->avf
);
337 cat
->avf
= avformat_alloc_context();
339 return AVERROR(ENOMEM
);
341 cat
->avf
->flags
|= avf
->flags
& ~AVFMT_FLAG_CUSTOM_IO
;
342 cat
->avf
->interrupt_callback
= avf
->interrupt_callback
;
344 if ((ret
= ff_copy_whiteblacklists(cat
->avf
, avf
)) < 0)
347 if ((ret
= avformat_open_input(&cat
->avf
, file
->url
, NULL
, NULL
)) < 0 ||
348 (ret
= avformat_find_stream_info(cat
->avf
, NULL
)) < 0) {
349 av_log(avf
, AV_LOG_ERROR
, "Impossible to open '%s'\n", file
->url
);
350 avformat_close_input(&cat
->avf
);
353 cat
->cur_file
= file
;
354 file
->start_time
= !fileno
? 0 :
355 cat
->files
[fileno
- 1].start_time
+
356 cat
->files
[fileno
- 1].duration
;
357 file
->file_start_time
= (cat
->avf
->start_time
== AV_NOPTS_VALUE
) ? 0 : cat
->avf
->start_time
;
358 file
->file_inpoint
= (file
->inpoint
== AV_NOPTS_VALUE
) ? file
->file_start_time
: file
->inpoint
;
359 file
->duration
= get_best_effort_duration(file
, cat
->avf
);
361 if (cat
->segment_time_metadata
) {
362 av_dict_set_int(&file
->metadata
, "lavf.concatdec.start_time", file
->start_time
, 0);
363 if (file
->duration
!= AV_NOPTS_VALUE
)
364 av_dict_set_int(&file
->metadata
, "lavf.concatdec.duration", file
->duration
, 0);
367 if ((ret
= match_streams(avf
)) < 0)
369 if (file
->inpoint
!= AV_NOPTS_VALUE
) {
370 if ((ret
= avformat_seek_file(cat
->avf
, -1, INT64_MIN
, file
->inpoint
, file
->inpoint
, 0)) < 0)
376 static int concat_read_close(AVFormatContext
*avf
)
378 ConcatContext
*cat
= avf
->priv_data
;
381 for (i
= 0; i
< cat
->nb_files
; i
++) {
382 av_freep(&cat
->files
[i
].url
);
383 for (j
= 0; j
< cat
->files
[i
].nb_streams
; j
++) {
384 if (cat
->files
[i
].streams
[j
].bsf
)
385 av_bsf_free(&cat
->files
[i
].streams
[j
].bsf
);
387 av_freep(&cat
->files
[i
].streams
);
388 av_dict_free(&cat
->files
[i
].metadata
);
391 avformat_close_input(&cat
->avf
);
392 av_freep(&cat
->files
);
396 static int concat_read_header(AVFormatContext
*avf
)
398 ConcatContext
*cat
= avf
->priv_data
;
400 uint8_t *cursor
, *keyword
;
402 unsigned nb_files_alloc
= 0;
403 ConcatFile
*file
= NULL
;
404 int64_t ret
, time
= 0;
406 av_bprint_init(&bp
, 0, AV_BPRINT_SIZE_UNLIMITED
);
408 while ((ret
= ff_read_line_to_bprint_overwrite(avf
->pb
, &bp
)) >= 0) {
411 keyword
= get_keyword(&cursor
);
412 if (!*keyword
|| *keyword
== '#')
415 if (!strcmp(keyword
, "file")) {
416 char *filename
= av_get_token((const char **)&cursor
, SPACE_CHARS
);
418 av_log(avf
, AV_LOG_ERROR
, "Line %d: filename required\n", line
);
419 FAIL(AVERROR_INVALIDDATA
);
421 if ((ret
= add_file(avf
, filename
, &file
, &nb_files_alloc
)) < 0)
423 } else if (!strcmp(keyword
, "duration") || !strcmp(keyword
, "inpoint") || !strcmp(keyword
, "outpoint")) {
424 char *dur_str
= get_keyword(&cursor
);
427 av_log(avf
, AV_LOG_ERROR
, "Line %d: %s without file\n",
429 FAIL(AVERROR_INVALIDDATA
);
431 if ((ret
= av_parse_time(&dur
, dur_str
, 1)) < 0) {
432 av_log(avf
, AV_LOG_ERROR
, "Line %d: invalid %s '%s'\n",
433 line
, keyword
, dur_str
);
436 if (!strcmp(keyword
, "duration"))
437 file
->user_duration
= dur
;
438 else if (!strcmp(keyword
, "inpoint"))
440 else if (!strcmp(keyword
, "outpoint"))
441 file
->outpoint
= dur
;
442 } else if (!strcmp(keyword
, "file_packet_metadata")) {
445 av_log(avf
, AV_LOG_ERROR
, "Line %d: %s without file\n",
447 FAIL(AVERROR_INVALIDDATA
);
449 metadata
= av_get_token((const char **)&cursor
, SPACE_CHARS
);
451 av_log(avf
, AV_LOG_ERROR
, "Line %d: packet metadata required\n", line
);
452 FAIL(AVERROR_INVALIDDATA
);
454 if ((ret
= av_dict_parse_string(&file
->metadata
, metadata
, "=", "", 0)) < 0) {
455 av_log(avf
, AV_LOG_ERROR
, "Line %d: failed to parse metadata string\n", line
);
457 FAIL(AVERROR_INVALIDDATA
);
460 } else if (!strcmp(keyword
, "stream")) {
461 if (!avformat_new_stream(avf
, NULL
))
462 FAIL(AVERROR(ENOMEM
));
463 } else if (!strcmp(keyword
, "exact_stream_id")) {
464 if (!avf
->nb_streams
) {
465 av_log(avf
, AV_LOG_ERROR
, "Line %d: exact_stream_id without stream\n",
467 FAIL(AVERROR_INVALIDDATA
);
469 avf
->streams
[avf
->nb_streams
- 1]->id
=
470 strtol(get_keyword(&cursor
), NULL
, 0);
471 } else if (!strcmp(keyword
, "ffconcat")) {
472 char *ver_kw
= get_keyword(&cursor
);
473 char *ver_val
= get_keyword(&cursor
);
474 if (strcmp(ver_kw
, "version") || strcmp(ver_val
, "1.0")) {
475 av_log(avf
, AV_LOG_ERROR
, "Line %d: invalid version\n", line
);
476 FAIL(AVERROR_INVALIDDATA
);
481 av_log(avf
, AV_LOG_ERROR
, "Line %d: unknown keyword '%s'\n",
483 FAIL(AVERROR_INVALIDDATA
);
486 if (ret
!= AVERROR_EOF
&& ret
< 0)
489 FAIL(AVERROR_INVALIDDATA
);
491 for (i
= 0; i
< cat
->nb_files
; i
++) {
492 if (cat
->files
[i
].start_time
== AV_NOPTS_VALUE
)
493 cat
->files
[i
].start_time
= time
;
495 time
= cat
->files
[i
].start_time
;
496 if (cat
->files
[i
].user_duration
== AV_NOPTS_VALUE
) {
497 if (cat
->files
[i
].inpoint
== AV_NOPTS_VALUE
|| cat
->files
[i
].outpoint
== AV_NOPTS_VALUE
||
498 cat
->files
[i
].outpoint
- (uint64_t)cat
->files
[i
].inpoint
!= av_sat_sub64(cat
->files
[i
].outpoint
, cat
->files
[i
].inpoint
)
501 cat
->files
[i
].user_duration
= cat
->files
[i
].outpoint
- cat
->files
[i
].inpoint
;
503 cat
->files
[i
].duration
= cat
->files
[i
].user_duration
;
504 if (time
+ (uint64_t)cat
->files
[i
].user_duration
> INT64_MAX
)
505 return AVERROR_INVALIDDATA
;
506 time
+= cat
->files
[i
].user_duration
;
508 if (i
== cat
->nb_files
) {
509 avf
->duration
= time
;
513 cat
->stream_match_mode
= avf
->nb_streams
? MATCH_EXACT_ID
:
515 if ((ret
= open_file(avf
, 0)) < 0)
517 av_bprint_finalize(&bp
, NULL
);
521 av_bprint_finalize(&bp
, NULL
);
522 concat_read_close(avf
);
526 static int open_next_file(AVFormatContext
*avf
)
528 ConcatContext
*cat
= avf
->priv_data
;
529 unsigned fileno
= cat
->cur_file
- cat
->files
;
531 cat
->cur_file
->duration
= get_best_effort_duration(cat
->cur_file
, cat
->avf
);
533 if (++fileno
>= cat
->nb_files
) {
537 return open_file(avf
, fileno
);
540 static int filter_packet(AVFormatContext
*avf
, ConcatStream
*cs
, AVPacket
*pkt
)
545 ret
= av_bsf_send_packet(cs
->bsf
, pkt
);
547 av_log(avf
, AV_LOG_ERROR
, "h264_mp4toannexb filter "
548 "failed to send input packet\n");
553 ret
= av_bsf_receive_packet(cs
->bsf
, pkt
);
555 if (ret
< 0 && (ret
!= AVERROR(EAGAIN
) && ret
!= AVERROR_EOF
)) {
556 av_log(avf
, AV_LOG_ERROR
, "h264_mp4toannexb filter "
557 "failed to receive output packet\n");
564 /* Returns true if the packet dts is greater or equal to the specified outpoint. */
565 static int packet_after_outpoint(ConcatContext
*cat
, AVPacket
*pkt
)
567 if (cat
->cur_file
->outpoint
!= AV_NOPTS_VALUE
&& pkt
->dts
!= AV_NOPTS_VALUE
) {
568 return av_compare_ts(pkt
->dts
, cat
->avf
->streams
[pkt
->stream_index
]->time_base
,
569 cat
->cur_file
->outpoint
, AV_TIME_BASE_Q
) >= 0;
574 static int concat_read_packet(AVFormatContext
*avf
, AVPacket
*pkt
)
576 ConcatContext
*cat
= avf
->priv_data
;
589 ret
= av_read_frame(cat
->avf
, pkt
);
590 if (ret
== AVERROR_EOF
) {
591 if ((ret
= open_next_file(avf
)) < 0)
597 if ((ret
= match_streams(avf
)) < 0) {
600 if (packet_after_outpoint(cat
, pkt
)) {
601 av_packet_unref(pkt
);
602 if ((ret
= open_next_file(avf
)) < 0)
606 cs
= &cat
->cur_file
->streams
[pkt
->stream_index
];
607 if (cs
->out_stream_index
< 0) {
608 av_packet_unref(pkt
);
613 if ((ret
= filter_packet(avf
, cs
, pkt
)) < 0)
616 st
= cat
->avf
->streams
[pkt
->stream_index
];
617 av_log(avf
, AV_LOG_DEBUG
, "file:%d stream:%d pts:%s pts_time:%s dts:%s dts_time:%s",
618 (unsigned)(cat
->cur_file
- cat
->files
), pkt
->stream_index
,
619 av_ts2str(pkt
->pts
), av_ts2timestr(pkt
->pts
, &st
->time_base
),
620 av_ts2str(pkt
->dts
), av_ts2timestr(pkt
->dts
, &st
->time_base
));
622 delta
= av_rescale_q(cat
->cur_file
->start_time
- cat
->cur_file
->file_inpoint
,
624 cat
->avf
->streams
[pkt
->stream_index
]->time_base
);
625 if (pkt
->pts
!= AV_NOPTS_VALUE
)
627 if (pkt
->dts
!= AV_NOPTS_VALUE
)
629 av_log(avf
, AV_LOG_DEBUG
, " -> pts:%s pts_time:%s dts:%s dts_time:%s\n",
630 av_ts2str(pkt
->pts
), av_ts2timestr(pkt
->pts
, &st
->time_base
),
631 av_ts2str(pkt
->dts
), av_ts2timestr(pkt
->dts
, &st
->time_base
));
632 if (cat
->cur_file
->metadata
) {
634 char* packed_metadata
= av_packet_pack_dictionary(cat
->cur_file
->metadata
, &metadata_len
);
635 if (!packed_metadata
)
636 return AVERROR(ENOMEM
);
637 ret
= av_packet_add_side_data(pkt
, AV_PKT_DATA_STRINGS_METADATA
,
638 packed_metadata
, metadata_len
);
640 av_freep(&packed_metadata
);
645 if (cat
->cur_file
->duration
== AV_NOPTS_VALUE
&& st
->cur_dts
!= AV_NOPTS_VALUE
) {
646 int64_t next_dts
= av_rescale_q(st
->cur_dts
, st
->time_base
, AV_TIME_BASE_Q
);
647 if (cat
->cur_file
->next_dts
== AV_NOPTS_VALUE
|| next_dts
> cat
->cur_file
->next_dts
) {
648 cat
->cur_file
->next_dts
= next_dts
;
652 pkt
->stream_index
= cs
->out_stream_index
;
656 static void rescale_interval(AVRational tb_in
, AVRational tb_out
,
657 int64_t *min_ts
, int64_t *ts
, int64_t *max_ts
)
659 *ts
= av_rescale_q (* ts
, tb_in
, tb_out
);
660 *min_ts
= av_rescale_q_rnd(*min_ts
, tb_in
, tb_out
,
661 AV_ROUND_UP
| AV_ROUND_PASS_MINMAX
);
662 *max_ts
= av_rescale_q_rnd(*max_ts
, tb_in
, tb_out
,
663 AV_ROUND_DOWN
| AV_ROUND_PASS_MINMAX
);
666 static int try_seek(AVFormatContext
*avf
, int stream
,
667 int64_t min_ts
, int64_t ts
, int64_t max_ts
, int flags
)
669 ConcatContext
*cat
= avf
->priv_data
;
670 int64_t t0
= cat
->cur_file
->start_time
- cat
->cur_file
->file_inpoint
;
673 min_ts
= min_ts
== INT64_MIN
? INT64_MIN
: min_ts
- t0
;
674 max_ts
= max_ts
== INT64_MAX
? INT64_MAX
: max_ts
- t0
;
676 if (stream
>= cat
->avf
->nb_streams
)
678 rescale_interval(AV_TIME_BASE_Q
, cat
->avf
->streams
[stream
]->time_base
,
679 &min_ts
, &ts
, &max_ts
);
681 return avformat_seek_file(cat
->avf
, stream
, min_ts
, ts
, max_ts
, flags
);
684 static int real_seek(AVFormatContext
*avf
, int stream
,
685 int64_t min_ts
, int64_t ts
, int64_t max_ts
, int flags
, AVFormatContext
*cur_avf
)
687 ConcatContext
*cat
= avf
->priv_data
;
688 int ret
, left
, right
;
691 if (stream
>= avf
->nb_streams
)
692 return AVERROR(EINVAL
);
693 rescale_interval(avf
->streams
[stream
]->time_base
, AV_TIME_BASE_Q
,
694 &min_ts
, &ts
, &max_ts
);
698 right
= cat
->nb_files
;
700 /* Always support seek to start */
703 else if (!cat
->seekable
)
704 return AVERROR(ESPIPE
); /* XXX: can we use it? */
706 while (right
- left
> 1) {
707 int mid
= (left
+ right
) / 2;
708 if (ts
< cat
->files
[mid
].start_time
)
714 if (cat
->cur_file
!= &cat
->files
[left
]) {
715 if ((ret
= open_file(avf
, left
)) < 0)
721 ret
= try_seek(avf
, stream
, min_ts
, ts
, max_ts
, flags
);
723 left
< cat
->nb_files
- 1 &&
724 cat
->files
[left
+ 1].start_time
< max_ts
) {
725 if (cat
->cur_file
== &cat
->files
[left
])
727 if ((ret
= open_file(avf
, left
+ 1)) < 0)
729 ret
= try_seek(avf
, stream
, min_ts
, ts
, max_ts
, flags
);
734 static int concat_seek(AVFormatContext
*avf
, int stream
,
735 int64_t min_ts
, int64_t ts
, int64_t max_ts
, int flags
)
737 ConcatContext
*cat
= avf
->priv_data
;
738 ConcatFile
*cur_file_saved
= cat
->cur_file
;
739 AVFormatContext
*cur_avf_saved
= cat
->avf
;
742 if (flags
& (AVSEEK_FLAG_BYTE
| AVSEEK_FLAG_FRAME
))
743 return AVERROR(ENOSYS
);
745 if ((ret
= real_seek(avf
, stream
, min_ts
, ts
, max_ts
, flags
, cur_avf_saved
)) < 0) {
746 if (cat
->cur_file
!= cur_file_saved
) {
748 avformat_close_input(&cat
->avf
);
750 cat
->avf
= cur_avf_saved
;
751 cat
->cur_file
= cur_file_saved
;
753 if (cat
->cur_file
!= cur_file_saved
) {
754 avformat_close_input(&cur_avf_saved
);
761 #define OFFSET(x) offsetof(ConcatContext, x)
762 #define DEC AV_OPT_FLAG_DECODING_PARAM
764 static const AVOption options
[] = {
765 { "safe", "enable safe mode",
766 OFFSET(safe
), AV_OPT_TYPE_BOOL
, {.i64
= 1}, -1, 1, DEC
},
767 { "auto_convert", "automatically convert bitstream format",
768 OFFSET(auto_convert
), AV_OPT_TYPE_BOOL
, {.i64
= 1}, 0, 1, DEC
},
769 { "segment_time_metadata", "output file segment start time and duration as packet metadata",
770 OFFSET(segment_time_metadata
), AV_OPT_TYPE_BOOL
, {.i64
= 0}, 0, 1, DEC
},
774 static const AVClass concat_class
= {
775 .class_name
= "concat demuxer",
776 .item_name
= av_default_item_name
,
778 .version
= LIBAVUTIL_VERSION_INT
,
782 AVInputFormat ff_concat_demuxer
= {
784 .long_name
= NULL_IF_CONFIG_SMALL("Virtual concatenation script"),
785 .priv_data_size
= sizeof(ConcatContext
),
786 .read_probe
= concat_probe
,
787 .read_header
= concat_read_header
,
788 .read_packet
= concat_read_packet
,
789 .read_close
= concat_read_close
,
790 .read_seek2
= concat_seek
,
791 .priv_class
= &concat_class
,