From: Clément Bœsch <clem...@stupeflix.com> --- Another approach this time: exporting a filtergraph from the demuxer (when requested) and inserting it in the tools automatically.
FIXME: doesn't work when using -ss with ffmpeg, maybe because it's shifting the timestamps of the frames before feeding them to libavfilter. I probably need guidance here. TODO: add -honor_timeline bool option in ffmpeg --- ffmpeg_filter.c | 29 +++++++++++++++++++++ ffmpeg_opt.c | 8 +++++- ffplay.c | 46 ++++++++++++++++++++++++++++++++- libavformat/mov.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 155 insertions(+), 4 deletions(-) diff --git a/ffmpeg_filter.c b/ffmpeg_filter.c index 264840b..83a6473 100644 --- a/ffmpeg_filter.c +++ b/ffmpeg_filter.c @@ -623,6 +623,29 @@ static int sub2video_prepare(InputStream *ist) return 0; } +static int insert_timeline_graph(const AVStream *st, AVFilterContext **last_filter) +{ + int ret; + AVFilterGraph *graph = (*last_filter)->graph; + const AVDictionaryEntry *tl_tag = av_dict_get(st->metadata, "timeline", NULL, 0); + + if (tl_tag) { + AVFilterInOut *inputs, *outputs; + + if ((ret = avfilter_graph_parse2(graph, tl_tag->value, &inputs, &outputs)) < 0) { + av_log(NULL, AV_LOG_ERROR, "Unable to parse timeline graph\n"); + return ret; + } + ret = avfilter_link(*last_filter, 0, inputs[0].filter_ctx, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Unable to link the end of the timeline graph to the last inserted filter\n"); + return ret; + } + *last_filter = outputs[0].filter_ctx; + } + return 0; +} + static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter, AVFilterInOut *in) { @@ -676,6 +699,9 @@ static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter, return ret; last_filter = ifilter->filter; + if ((ret = insert_timeline_graph(ist->st, &last_filter)) < 0) + return ret; + if (ist->framerate.num) { AVFilterContext *setpts; @@ -764,6 +790,9 @@ static int configure_input_audio_filter(FilterGraph *fg, InputFilter *ifilter, return ret; last_filter = ifilter->filter; + if ((ret = insert_timeline_graph(ist->st, &last_filter)) < 0) + return ret; + #define AUTO_INSERT_FILTER_INPUT(opt_name, filter_name, arg) do { \ AVFilterContext *filt_ctx; \ \ diff --git a/ffmpeg_opt.c b/ffmpeg_opt.c index ac93eb5..cd7eb46 100644 --- a/ffmpeg_opt.c +++ b/ffmpeg_opt.c @@ -804,7 +804,7 @@ static int open_input_file(OptionsContext *o, const char *filename) char * video_codec_name = NULL; char * audio_codec_name = NULL; char *subtitle_codec_name = NULL; - int scan_all_pmts_set = 0; + int scan_all_pmts_set = 0, ignore_editlist_set = 0; if (o->format) { if (!(file_iformat = av_find_input_format(o->format))) { @@ -879,6 +879,10 @@ static int open_input_file(OptionsContext *o, const char *filename) av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); scan_all_pmts_set = 1; } + if (!av_dict_get(o->g->format_opts, "ignore_editlist", NULL, AV_DICT_MATCH_CASE)) { + av_dict_set(&o->g->format_opts, "ignore_editlist", "lavfi_timeline", AV_DICT_DONT_OVERWRITE); + ignore_editlist_set = 1; + } /* open the input file with generic avformat function */ err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts); if (err < 0) { @@ -887,6 +891,8 @@ static int open_input_file(OptionsContext *o, const char *filename) } if (scan_all_pmts_set) av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); + if (ignore_editlist_set) + av_dict_set(&o->g->format_opts, "ignore_editlist", NULL, AV_DICT_MATCH_CASE); remove_avoptions(&o->g->format_opts, o->g->codec_opts); assert_avoptions(o->g->format_opts); diff --git a/ffplay.c b/ffplay.c index 1914a66..52cf686 100644 --- a/ffplay.c +++ b/ffplay.c @@ -348,6 +348,7 @@ static int nb_vfilters = 0; static char *afilters = NULL; #endif static int autorotate = 1; +static int honor_timeline = 1; /* current context */ static int is_full_screen; @@ -1952,6 +1953,36 @@ fail: return ret; } +static int insert_timeline_graph(const AVStream *st, AVFilterContext **last_filter) +{ + // XXX: configure_audio() seems to be called sometimes several times for + // the same stream, which might be NULL + if (!st) + return 0; + + if (honor_timeline) { + int ret; + AVFilterGraph *graph = (*last_filter)->graph; + const AVDictionaryEntry *tl_tag = av_dict_get(st->metadata, "timeline", NULL, 0); + + if (tl_tag) { + AVFilterInOut *inputs, *outputs; + + if ((ret = avfilter_graph_parse2(graph, tl_tag->value, &inputs, &outputs)) < 0) { + av_log(NULL, AV_LOG_ERROR, "Unable to parse timeline graph\n"); + return ret; + } + ret = avfilter_link(outputs[0].filter_ctx, 0, *last_filter, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Unable to link the end of the timeline graph to the last inserted filter\n"); + return ret; + } + *last_filter = inputs[0].filter_ctx; + } + } + return 0; +} + static int configure_video_filters(AVFilterGraph *graph, VideoState *is, const char *vfilters, AVFrame *frame) { static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE }; @@ -2031,6 +2062,9 @@ static int configure_video_filters(AVFilterGraph *graph, VideoState *is, const c } } + if ((ret = insert_timeline_graph(is->video_st, &last_filter)) < 0) + goto fail; + if ((ret = configure_filtergraph(graph, vfilters, filt_src, last_filter)) < 0) goto fail; @@ -2104,6 +2138,8 @@ static int configure_audio_filters(VideoState *is, const char *afilters, int for goto end; } + if ((ret = insert_timeline_graph(is->audio_st, &filt_asink)) < 0) + goto end; if ((ret = configure_filtergraph(is->agraph, afilters, filt_asrc, filt_asink)) < 0) goto end; @@ -2889,7 +2925,7 @@ static int read_thread(void *arg) AVDictionary **opts; int orig_nb_streams; SDL_mutex *wait_mutex = SDL_CreateMutex(); - int scan_all_pmts_set = 0; + int scan_all_pmts_set = 0, ignore_editlist_set = 0; memset(st_index, -1, sizeof(st_index)); is->last_video_stream = is->video_stream = -1; @@ -2903,6 +2939,11 @@ static int read_thread(void *arg) av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); scan_all_pmts_set = 1; } + if (CONFIG_AVFILTER && honor_timeline && + !av_dict_get(format_opts, "ignore_editlist", NULL, AV_DICT_MATCH_CASE)) { + av_dict_set(&format_opts, "ignore_editlist", "lavfi_timeline", AV_DICT_DONT_OVERWRITE); + ignore_editlist_set = 1; + } err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts); if (err < 0) { print_error(is->filename, err); @@ -2911,6 +2952,8 @@ static int read_thread(void *arg) } if (scan_all_pmts_set) av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); + if (ignore_editlist_set) + av_dict_set(&format_opts, "ignore_editlist", NULL, AV_DICT_MATCH_CASE); if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key); @@ -3709,6 +3752,7 @@ static const OptionDef options[] = { { "scodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &subtitle_codec_name }, "force subtitle decoder", "decoder_name" }, { "vcodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &video_codec_name }, "force video decoder", "decoder_name" }, { "autorotate", OPT_BOOL, { &autorotate }, "automatically rotate video", "" }, + { "honor_timeline", OPT_BOOL | OPT_EXPERT, { &honor_timeline }, "honor timelines", "" }, { NULL, }, }; diff --git a/libavformat/mov.c b/libavformat/mov.c index f2d4fa0..424a894 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -35,6 +35,7 @@ #include "libavutil/mathematics.h" #include "libavutil/time_internal.h" #include "libavutil/avstring.h" +#include "libavutil/bprint.h" #include "libavutil/dict.h" #include "libavutil/opt.h" #include "libavutil/timecode.h" @@ -2269,6 +2270,74 @@ static void mov_build_index(MOVContext *mov, AVStream *st) uint64_t stream_size = 0; if (sc->elst_count) { + + if (mov->ignore_editlist == -1) { + int i; + AVBPrint tl; + AVBPrint select; + AVBPrint setpts; + const MOVElst *elst = sc->elst_data; + + av_bprint_init(&tl, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprint_init(&select, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprint_init(&setpts, 0, AV_BPRINT_SIZE_UNLIMITED); + + for (i = 0; i < sc->elst_count; i++) { + int64_t gap; + const MOVElst *segment = &elst[i]; + const MOVElst *next = i < sc->elst_count - 1 ? &elst[i + 1] : NULL; + const MOVElst *prev = i > 0 ? &elst[i - 1] : NULL; + int64_t end = segment->duration ? segment->time + segment->duration : -1; + + if (!segment->duration && next) + end = next->time; + + if (select.str[0]) + av_bprintf(&select, "+"); + if (end == -1) + av_bprintf(&select, "gte(pts,%"PRId64")", segment->time); + else + av_bprintf(&select, "between(pts,%"PRId64",%"PRId64"-1)", + segment->time, end); + + if (segment->time == -1) + /* XXX: we are supposed to insert initial silence/emptiness here */ + gap = segment->duration; + else if (prev) + gap = segment->time - prev->time - prev->duration; + else + gap = segment->time; + gap *= segment->rate; + + if (gap) { + if (!*setpts.str) + av_bprintf(&setpts, "PTS"); + av_bprintf(&setpts, "-if(gte(PTS,%"PRId64"),%"PRId64",0)", + segment->time, gap); + } + } + + if (select.str[0] && av_bprint_is_complete(&select) && + setpts.str[0] && av_bprint_is_complete(&setpts)) { + + const char *tstr = st->codec->codec_type == AVMEDIA_TYPE_AUDIO ? "a" : ""; + + // FIXME: aselect should probably be made sample accurate + + av_bprintf(&tl, "[tl_in] %sselect='%s',%ssetpts='%s' [tl_out]", + tstr, select.str, tstr, setpts.str); + + if (av_bprint_is_complete(&tl)) + av_dict_set(&st->metadata, "timeline", tl.str, 0); + } + + av_bprint_finalize(&select, NULL); + av_bprint_finalize(&setpts, NULL); + av_bprint_finalize(&tl, NULL); + + } else { + // TODO: reindent + int i, edit_start_index = 0, unsupported = 0; int64_t empty_duration = 0; // empty duration of the first edit list entry int64_t start_time = 0; // start time of the media @@ -2303,6 +2372,7 @@ static void mov_build_index(MOVContext *mov, AVStream *st) st->codec->has_b_frames = 1; } } + } } /* only use old uncompressed audio chunk demuxing when stts specifies it */ @@ -3215,7 +3285,7 @@ static int mov_read_elst(MOVContext *c, AVIOContext *pb, MOVAtom atom) MOVStreamContext *sc; int i, edit_count, version; - if (c->fc->nb_streams < 1 || c->ignore_editlist) + if (c->fc->nb_streams < 1 || c->ignore_editlist == 1) return 0; sc = c->fc->streams[c->fc->nb_streams-1]->priv_data; @@ -4255,7 +4325,9 @@ static const AVOption mov_options[] = { OFFSET(use_absolute_path), FF_OPT_TYPE_INT, {.i64 = 0}, 0, 1, FLAGS}, {"ignore_editlist", "", OFFSET(ignore_editlist), FF_OPT_TYPE_INT, {.i64 = 0}, - 0, 1, FLAGS}, + -1, 1, FLAGS, "editlist_mode"}, + {"lavfi_timeline", "export timelines as libavfilter filtergraphs", + 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, FLAGS, "editlist_mode" }, {"use_mfra_for", "use mfra for fragment timestamps", OFFSET(use_mfra_for), FF_OPT_TYPE_INT, {.i64 = FF_MOV_FLAG_MFRA_AUTO}, -- 2.2.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel