diff --git a/Changelog b/Changelog index d0458964d6..72a51ea3e3 100644 --- a/Changelog +++ b/Changelog @@ -12,6 +12,8 @@ version : - 10% faster aac encoding on x86 and MIPS - sine audio filter source - WebP demuxing and decoding support +- new ffmpeg options -filter_script and -filter_complex_script, which allow a + filtergraph description to be read from a file version 1.2: diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi index ca5d652f84..d3bf40543b 100644 --- a/doc/ffmpeg.texi +++ b/doc/ffmpeg.texi @@ -359,6 +359,11 @@ syntax. See the @ref{filter_complex_option,,-filter_complex option} if you want to create filter graphs with multiple inputs and/or outputs. +@item -filter_script[:@var{stream_specifier}] @var{filename} (@emph{output,per-stream}) +This option is similar to @option{-filter}, the only difference is that its +argument is the name of the file from which a filtergraph description is to be +read. + @item -pre[:@var{stream_specifier}] @var{preset_name} (@emph{output,per-stream}) Specify the preset for matching stream(s). @@ -1049,6 +1054,11 @@ ffmpeg -filter_complex 'color=c=red' -t 5 out.mkv Define a complex filter graph, i.e. one with arbitrary number of inputs and/or outputs. Equivalent to @option{-filter_complex}. +@item -filter_complex_script @var{filename} (@emph{global}) +This option is similar to @option{-filter_complex}, the only difference is that +its argument is the name of the file from which a complex filtergraph +description is to be read. + @end table As a special exception, you can use a bitmap subtitle stream as input: it diff --git a/ffmpeg.c b/ffmpeg.c index fc186224e5..e17305c2fc 100644 --- a/ffmpeg.c +++ b/ffmpeg.c @@ -442,6 +442,7 @@ static void exit_program(void) av_freep(&filtergraphs[i]->outputs[j]); } av_freep(&filtergraphs[i]->outputs); + av_freep(&filtergraphs[i]->graph_desc); av_freep(&filtergraphs[i]); } av_freep(&filtergraphs); diff --git a/ffmpeg.h b/ffmpeg.h index 9a86b506a0..4b93ab1ad3 100644 --- a/ffmpeg.h +++ b/ffmpeg.h @@ -165,6 +165,8 @@ typedef struct OptionsContext { int nb_copy_prior_start; SpecifierOpt *filters; int nb_filters; + SpecifierOpt *filter_scripts; + int nb_filter_scripts; SpecifierOpt *reinit_filters; int nb_reinit_filters; SpecifierOpt *fix_sub_duration; diff --git a/ffmpeg_opt.c b/ffmpeg_opt.c index a134274a56..63a238ddfa 100644 --- a/ffmpeg_opt.c +++ b/ffmpeg_opt.c @@ -1103,6 +1103,59 @@ static void parse_matrix_coeffs(uint16_t *dest, const char *str) } } +/* read file contents into a string */ +static uint8_t *read_file(const char *filename) +{ + AVIOContext *pb = NULL; + AVIOContext *dyn_buf = NULL; + int ret = avio_open(&pb, filename, AVIO_FLAG_READ); + uint8_t buf[1024], *str; + + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error opening file %s.\n", filename); + return NULL; + } + + ret = avio_open_dyn_buf(&dyn_buf); + if (ret < 0) { + avio_closep(&pb); + return NULL; + } + while ((ret = avio_read(pb, buf, sizeof(buf))) > 0) + avio_write(dyn_buf, buf, ret); + avio_w8(dyn_buf, 0); + avio_closep(&pb); + + ret = avio_close_dyn_buf(dyn_buf, &str); + if (ret < 0) + return NULL; + return str; +} + +static char *get_ost_filters(OptionsContext *o, AVFormatContext *oc, + OutputStream *ost) +{ + AVStream *st = ost->st; + char *filter = NULL, *filter_script = NULL; + + MATCH_PER_STREAM_OPT(filter_scripts, str, filter_script, oc, st); + MATCH_PER_STREAM_OPT(filters, str, filter, oc, st); + + if (filter_script && filter) { + av_log(NULL, AV_LOG_ERROR, "Both -filter and -filter_script set for " + "output stream #%d:%d.\n", nb_output_files, st->index); + exit(1); + } + + if (filter_script) + return read_file(filter_script); + else if (filter) + return av_strdup(filter); + + return av_strdup(st->codec->codec_type == AVMEDIA_TYPE_VIDEO ? + "null" : "anull"); +} + static OutputStream *new_video_stream(OptionsContext *o, AVFormatContext *oc, int source_index) { AVStream *st; @@ -1125,7 +1178,6 @@ static OutputStream *new_video_stream(OptionsContext *o, AVFormatContext *oc, in char *frame_size = NULL; char *frame_aspect_ratio = NULL, *frame_pix_fmt = NULL; char *intra_matrix = NULL, *inter_matrix = NULL; - const char *filters = "null"; int do_pass = 0; int i; @@ -1236,8 +1288,10 @@ static OutputStream *new_video_stream(OptionsContext *o, AVFormatContext *oc, in ost->top_field_first = -1; MATCH_PER_STREAM_OPT(top_field_first, i, ost->top_field_first, oc, st); - MATCH_PER_STREAM_OPT(filters, str, filters, oc, st); - ost->avfilter = av_strdup(filters); + + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit(1); } else { MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, ost->copy_initial_nonkeyframes, oc ,st); } @@ -1260,7 +1314,6 @@ static OutputStream *new_audio_stream(OptionsContext *o, AVFormatContext *oc, in if (!ost->stream_copy) { char *sample_fmt = NULL; - const char *filters = "anull"; MATCH_PER_STREAM_OPT(audio_channels, i, audio_enc->channels, oc, st); @@ -1273,10 +1326,9 @@ static OutputStream *new_audio_stream(OptionsContext *o, AVFormatContext *oc, in MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st); - MATCH_PER_STREAM_OPT(filters, str, filters, oc, st); - - av_assert1(filters); - ost->avfilter = av_strdup(filters); + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit(1); /* check for channel mapping for this audio stream */ for (n = 0; n < o->nb_audio_channel_maps; n++) { @@ -2291,8 +2343,24 @@ static int opt_filter_complex(void *optctx, const char *opt, const char *arg) GROW_ARRAY(filtergraphs, nb_filtergraphs); if (!(filtergraphs[nb_filtergraphs - 1] = av_mallocz(sizeof(*filtergraphs[0])))) return AVERROR(ENOMEM); - filtergraphs[nb_filtergraphs - 1]->index = nb_filtergraphs - 1; - filtergraphs[nb_filtergraphs - 1]->graph_desc = arg; + filtergraphs[nb_filtergraphs - 1]->index = nb_filtergraphs - 1; + filtergraphs[nb_filtergraphs - 1]->graph_desc = av_strdup(arg); + if (!filtergraphs[nb_filtergraphs - 1]->graph_desc) + return AVERROR(ENOMEM); + return 0; +} + +static int opt_filter_complex_script(void *optctx, const char *opt, const char *arg) +{ + uint8_t *graph_desc = read_file(arg); + if (!graph_desc) + return AVERROR(EINVAL); + + GROW_ARRAY(filtergraphs, nb_filtergraphs); + if (!(filtergraphs[nb_filtergraphs - 1] = av_mallocz(sizeof(*filtergraphs[0])))) + return AVERROR(ENOMEM); + filtergraphs[nb_filtergraphs - 1]->index = nb_filtergraphs - 1; + filtergraphs[nb_filtergraphs - 1]->graph_desc = graph_desc; return 0; } @@ -2590,12 +2658,16 @@ const OptionDef options[] = { "set profile", "profile" }, { "filter", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(filters) }, "set stream filtergraph", "filter_graph" }, + { "filter_script", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(filter_scripts) }, + "read stream filtergraph description from a file", "filename" }, { "reinit_filter", HAS_ARG | OPT_INT | OPT_SPEC | OPT_INPUT, { .off = OFFSET(reinit_filters) }, "reinit filtergraph on input parameter changes", "" }, { "filter_complex", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_complex }, "create a complex filtergraph", "graph_description" }, { "lavfi", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_complex }, "create a complex filtergraph", "graph_description" }, + { "filter_complex_script", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_complex_script }, + "read complex filtergraph description from a file", "filename" }, { "stats", OPT_BOOL, { &print_stats }, "print progress report during encoding", }, { "attach", HAS_ARG | OPT_PERFILE | OPT_EXPERT | diff --git a/tests/fate/filter.mak b/tests/fate/filter.mak index 4b1b056a8b..46ff5c1450 100644 --- a/tests/fate/filter.mak +++ b/tests/fate/filter.mak @@ -1,3 +1,7 @@ +FILTERDEMDEC = $(call ALLYES, $(1)_FILTER $(2)_DEMUXER $(3)_DECODER) +FILTERDEMDECMUX = $(call ALLYES, $(1)_FILTER $(2)_DEMUXER $(3)_DECODER $(4)_MUXER) +FILTERDEMDECENCMUX = $(call ALLYES, $(1)_FILTER $(2)_DEMUXER $(3)_DECODER $(4)_ENCODER $(5)_MUXER) + FATE_AMIX += fate-filter-amix-simple fate-filter-amix-simple: CMD = ffmpeg -filter_complex amix -i $(SRC) -ss 3 -i $(SRC1) -f f32le - fate-filter-amix-simple: REF = $(SAMPLES)/filter/amix_simple.pcm @@ -18,9 +22,9 @@ $(FATE_AMIX): SRC1 = $(TARGET_PATH)/tests/data/asynth-44100-2-2.wav $(FATE_AMIX): CMP = oneoff $(FATE_AMIX): CMP_UNIT = f32 -FATE_FILTER-$(CONFIG_AMIX_FILTER) += $(FATE_AMIX) +FATE_FILTER-$(call FILTERDEMDECENCMUX, AMIX, WAV, PCM_S16LE, PCM_F32LE, PCM_F32LE) += $(FATE_AMIX) -FATE_FILTER-$(CONFIG_ASYNCTS_FILTER) += fate-filter-asyncts +FATE_FILTER-$(call FILTERDEMDECMUX, ASYNCTS, FLV, NELLYMOSER, PCM_S16LE) += fate-filter-asyncts fate-filter-asyncts: SRC = $(SAMPLES)/nellymoser/nellymoser-discont.flv fate-filter-asyncts: CMD = pcm -analyzeduration 10000000 -i $(SRC) -af asyncts fate-filter-asyncts: CMP = oneoff @@ -34,7 +38,7 @@ fate-filter-aresample: REF = $(SAMPLES)/nellymoser/nellymoser-discont.pcm fate-filter-delogo: CMD = framecrc -i $(SAMPLES)/real/rv30.rm -vf perms=random,delogo=show=0:x=290:y=25:w=26:h=16 -an -FATE_FILTER-$(call ALLYES, PERMS_FILTER DELOGO_FILTER) += fate-filter-delogo +FATE_FILTER-$(call ALLYES, PERMS_FILTER DELOGO_FILTER RM_DEMUXER RV30_DECODER) += fate-filter-delogo FATE_YADIF += fate-filter-yadif-mode0 fate-filter-yadif-mode0: CMD = framecrc -flags bitexact -idct simple -i $(SAMPLES)/mpeg2/mpeg2_field_encoding.ts -vframes 30 -vf yadif=0 @@ -42,7 +46,7 @@ fate-filter-yadif-mode0: CMD = framecrc -flags bitexact -idct simple -i $(SAMPLE FATE_YADIF += fate-filter-yadif-mode1 fate-filter-yadif-mode1: CMD = framecrc -flags bitexact -idct simple -i $(SAMPLES)/mpeg2/mpeg2_field_encoding.ts -vframes 59 -vf yadif=1 -FATE_FILTER-$(CONFIG_YADIF_FILTER) += $(FATE_YADIF) +FATE_FILTER-$(call FILTERDEMDEC, YADIF, MPEGTS, MPEG2VIDEO) += $(FATE_YADIF) FATE_HQDN3D += fate-filter-hqdn3d fate-filter-hqdn3d: CMD = framecrc -idct simple -i $(SAMPLES)/smjpeg/scenwin.mjpg -vf perms=random,hqdn3d -an