diff --git a/Changelog b/Changelog index 7481a7ccca8c319e78d56aebef151c1e60e372fa..3e8293f0aed8bd1fe9f0a707a24d36ba29224dd6 100644 --- a/Changelog +++ b/Changelog @@ -30,6 +30,7 @@ version <next>: - decent native animated GIF encoding - asetrate filter - interleave filter +- timeline editing with filters version 1.2: diff --git a/cmdutils.c b/cmdutils.c index cc886977e82ddcaf3b96e0bcc5ba6c58daaa3b9b..b9985d620e62ccbe07146912d3bd2aca2cf449a7 100644 --- a/cmdutils.c +++ b/cmdutils.c @@ -1683,6 +1683,8 @@ static void show_help_filter(const char *name) if (f->priv_class) show_help_children(f->priv_class, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM); + if (f->flags & AVFILTER_FLAG_SUPPORT_TIMELINE) + printf("This filter has support for timeline through the 'enable' option.\n"); #else av_log(NULL, AV_LOG_ERROR, "Build without libavfilter; " "can not to satisfy request\n"); diff --git a/doc/filters.texi b/doc/filters.texi index ada992e08bfbdba206c61c075082b4f9020a2806..798d04befcc9a702b5d50a2df74c63409f045fde 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -267,6 +267,36 @@ See the ``Quoting and escaping'' section in the ffmpeg-utils manual for more information about the escaping and quoting rules adopted by FFmpeg. +@chapter Timeline editing + +Some filters support a generic @option{enable} option. For the filters +supporting timeline editing, this option can be set to an expression which is +evaluated before sending a frame to the filter. If the evaluation is non-zero, +the filter will be enabled, otherwise the frame will be sent unchanged to the +next filter in the filtergraph. + +The expression accepts the following values: +@table @samp +@item t +timestamp expressed in seconds, NAN if the input timestamp is unknown + +@item n +sequential number of the input frame, starting from 0 + +@item pos +the position in the file of the input frame, NAN if unknown +@end table + +Like any other filtering option, the @option{enable} option follows the same +rules. + +For example, to enable a denoiser filter (@ref{hqdn3d}) from 10 seconds to 3 +minutes, and a @ref{curves} filter starting at 3 seconds: +@example +hqdn3d = enable='between(t,10,3*60)', +curves = enable='gte(t,3)' : preset=cross_process +@end example + @c man end FILTERGRAPH DESCRIPTION @chapter Audio Filters @@ -2409,6 +2439,7 @@ indicates never reset and return the largest area encountered during playback. @end table +@anchor{curves} @section curves Apply color adjustments using curves. @@ -4013,6 +4044,7 @@ ffplay -i input -vf histogram @end itemize +@anchor{hqdn3d} @section hqdn3d High precision/quality 3d denoise filter. This filter aims to reduce diff --git a/libavfilter/af_volume.c b/libavfilter/af_volume.c index 7d2b9bad55748a5724786588f5220fe318187804..a55e1a3518df641ae7fff73ec3f68848a2473869 100644 --- a/libavfilter/af_volume.c +++ b/libavfilter/af_volume.c @@ -296,4 +296,5 @@ AVFilter avfilter_af_volume = { .init = init, .inputs = avfilter_af_volume_inputs, .outputs = avfilter_af_volume_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c index 43340d10045a28234fbc5e619e598e75bbc0af6d..0c6f1f1690fd12cf38a81317201e840b9dd023e7 100644 --- a/libavfilter/avfilter.c +++ b/libavfilter/avfilter.c @@ -23,6 +23,7 @@ #include "libavutil/avstring.h" #include "libavutil/channel_layout.h" #include "libavutil/common.h" +#include "libavutil/eval.h" #include "libavutil/imgutils.h" #include "libavutil/opt.h" #include "libavutil/pixdesc.h" @@ -483,12 +484,20 @@ static const AVClass *filter_child_class_next(const AVClass *prev) return NULL; } +#define OFFSET(x) offsetof(AVFilterContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM +static const AVOption filters_common_options[] = { + { "enable", "set enable expression", OFFSET(enable_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, + { NULL } +}; + static const AVClass avfilter_class = { .class_name = "AVFilter", .item_name = default_filter_name, .version = LIBAVUTIL_VERSION_INT, .category = AV_CLASS_CATEGORY_FILTER, .child_next = filter_child_next, + .option = filters_common_options, .child_class_next = filter_child_class_next, }; @@ -618,9 +627,16 @@ void avfilter_free(AVFilterContext *filter) while(filter->command_queue){ ff_command_queue_pop(filter); } + av_opt_free(filter); + av_expr_free(filter->enable); + filter->enable = NULL; + av_freep(&filter->var_values); av_free(filter); } +static const char *const var_names[] = { "t", "n", "pos", NULL }; +enum { VAR_T, VAR_N, VAR_POS, VAR_VARS_NB }; + static int process_options(AVFilterContext *ctx, AVDictionary **options, const char *args) { @@ -630,6 +646,8 @@ static int process_options(AVFilterContext *ctx, AVDictionary **options, const char *key; int offset= -1; + av_opt_set_defaults(ctx); + if (!args) return 0; @@ -665,6 +683,12 @@ static int process_options(AVFilterContext *ctx, AVDictionary **options, } av_log(ctx, AV_LOG_DEBUG, "Setting '%s' to value '%s'\n", key, value); + + if (av_opt_find(ctx, key, NULL, 0, 0)) { + ret = av_opt_set(ctx, key, value, 0); + if (ret < 0) + return ret; + } else { av_dict_set(options, key, value, 0); if ((ret = av_opt_set(ctx->priv, key, value, 0)) < 0) { if (!av_opt_find(ctx->priv, key, NULL, 0, AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ)) { @@ -675,11 +699,27 @@ static int process_options(AVFilterContext *ctx, AVDictionary **options, return ret; } } + } av_free(value); av_free(parsed_key); count++; } + + if (ctx->enable_str) { + if (!(ctx->filter->flags & AVFILTER_FLAG_SUPPORT_TIMELINE)) { + av_log(ctx, AV_LOG_ERROR, "Timeline ('enable' option) not supported " + "with filter '%s'\n", ctx->filter->name); + return AVERROR_PATCHWELCOME; + } + ctx->var_values = av_calloc(VAR_VARS_NB, sizeof(*ctx->var_values)); + if (!ctx->var_values) + return AVERROR(ENOMEM); + ret = av_expr_parse((AVExpr**)&ctx->enable, ctx->enable_str, var_names, + NULL, NULL, NULL, NULL, 0, ctx->priv); + if (ret < 0) + return ret; + } return count; } @@ -852,6 +892,7 @@ static int default_filter_frame(AVFilterLink *link, AVFrame *frame) static int ff_filter_frame_framed(AVFilterLink *link, AVFrame *frame) { int (*filter_frame)(AVFilterLink *, AVFrame *); + AVFilterContext *dstctx = link->dst; AVFilterPad *dst = link->dstpad; AVFrame *out; int ret; @@ -914,6 +955,15 @@ static int ff_filter_frame_framed(AVFilterLink *link, AVFrame *frame) } pts = out->pts; + if (dstctx->enable_str) { + int64_t pos = av_frame_get_pkt_pos(out); + dstctx->var_values[VAR_N] = link->frame_count; + dstctx->var_values[VAR_T] = pts == AV_NOPTS_VALUE ? NAN : pts * av_q2d(link->time_base); + dstctx->var_values[VAR_POS] = pos == -1 ? NAN : pos; + if (!av_expr_eval(dstctx->enable, dstctx->var_values, NULL)) + filter_frame = dst->passthrough_filter_frame ? dst->passthrough_filter_frame + : default_filter_frame; + } ret = filter_frame(link, out); link->frame_count++; link->frame_requested = 0; diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index 047208c02be473239e8905b758efdd63546fcd5f..e7e979eed18a6a1245038a5dde16a768f928bb85 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -385,6 +385,19 @@ struct AVFilterPad { int needs_fifo; int needs_writable; + + /** + * Passthrough filtering callback. + * + * If a filter supports timeline editing (in case + * AVFILTER_FLAG_SUPPORT_TIMELINE is enabled) then it can implement a + * custom passthrough callback to update its local context (for example to + * keep a frame reference, or simply send the filter to a custom outlink). + * The filter must not do any change to the frame in this callback. + * + * Input pads only. + */ + int (*passthrough_filter_frame)(AVFilterLink *link, AVFrame *frame); }; #endif @@ -428,6 +441,12 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx); * the options supplied to it. */ #define AVFILTER_FLAG_DYNAMIC_OUTPUTS (1 << 1) +/** + * Some filters support a generic "enable" expression option that can be used + * to enable or disable a filter in the timeline. Filters supporting this + * option have this flag set. + */ +#define AVFILTER_FLAG_SUPPORT_TIMELINE (1 << 16) /** * Filter definition. This defines the pads a filter contains, and all the @@ -522,7 +541,7 @@ typedef struct AVFilter { /** An instance of a filter */ struct AVFilterContext { - const AVClass *av_class; ///< needed for av_log() + const AVClass *av_class; ///< needed for av_log() and filters common options const AVFilter *filter; ///< the AVFilter of which this is an instance @@ -547,6 +566,10 @@ struct AVFilterContext { struct AVFilterGraph *graph; ///< filtergraph this filter belongs to struct AVFilterCommand *command_queue; + + char *enable_str; ///< enable expression string + void *enable; ///< parsed expression (AVExpr*) + double *var_values; ///< variable values for the enable expression }; /** diff --git a/libavfilter/vf_boxblur.c b/libavfilter/vf_boxblur.c index 758964476bb7f7ab9cbde037f956dd34794795ce..b0106708c3380abb3d205e99153ec9e0086a9347 100644 --- a/libavfilter/vf_boxblur.c +++ b/libavfilter/vf_boxblur.c @@ -383,4 +383,5 @@ AVFilter avfilter_vf_boxblur = { .inputs = avfilter_vf_boxblur_inputs, .outputs = avfilter_vf_boxblur_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_colormatrix.c b/libavfilter/vf_colormatrix.c index d25ce60a97627e0bba6e296786c20e7cfa58adc6..8db5fcf8d151e3ec9b995ae2918447960be7bf88 100644 --- a/libavfilter/vf_colormatrix.c +++ b/libavfilter/vf_colormatrix.c @@ -385,4 +385,5 @@ AVFilter avfilter_vf_colormatrix = { .inputs = colormatrix_inputs, .outputs = colormatrix_outputs, .priv_class = &colormatrix_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_cropdetect.c b/libavfilter/vf_cropdetect.c index 3d109f8a592d8295e7a05fe2a70b527403d07564..cb170d08fb58b597101fcb66d7e926b6a4eddec6 100644 --- a/libavfilter/vf_cropdetect.c +++ b/libavfilter/vf_cropdetect.c @@ -233,4 +233,5 @@ AVFilter avfilter_vf_cropdetect = { .query_formats = query_formats, .inputs = avfilter_vf_cropdetect_inputs, .outputs = avfilter_vf_cropdetect_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_curves.c b/libavfilter/vf_curves.c index 9f5d8bd16636f09ab4c09e14e8a292cfa9237599..626b4ea324904cb4fd20702cae7ccd1166a7fb90 100644 --- a/libavfilter/vf_curves.c +++ b/libavfilter/vf_curves.c @@ -514,4 +514,5 @@ AVFilter avfilter_vf_curves = { .inputs = curves_inputs, .outputs = curves_outputs, .priv_class = &curves_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_drawbox.c b/libavfilter/vf_drawbox.c index edb77cc79fb429014d112432842166a1aa76d91e..c2ffbf32107d6f902a33a1ce4bfe60af88339b36 100644 --- a/libavfilter/vf_drawbox.c +++ b/libavfilter/vf_drawbox.c @@ -181,4 +181,5 @@ AVFilter avfilter_vf_drawbox = { .query_formats = query_formats, .inputs = avfilter_vf_drawbox_inputs, .outputs = avfilter_vf_drawbox_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_edgedetect.c b/libavfilter/vf_edgedetect.c index f29c7ee9441912d3f5400a76090cd4a6098d4916..b4698a8fe34adcab77270a14fa5faae04f2679b2 100644 --- a/libavfilter/vf_edgedetect.c +++ b/libavfilter/vf_edgedetect.c @@ -327,4 +327,5 @@ AVFilter avfilter_vf_edgedetect = { .inputs = edgedetect_inputs, .outputs = edgedetect_outputs, .priv_class = &edgedetect_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_gradfun.c b/libavfilter/vf_gradfun.c index 9e21560de975d8c4750fceb574962f0a385babf8..48ac378e6abd286ef5e14cf3b4a695f3846032f2 100644 --- a/libavfilter/vf_gradfun.c +++ b/libavfilter/vf_gradfun.c @@ -260,4 +260,5 @@ AVFilter avfilter_vf_gradfun = { .query_formats = query_formats, .inputs = avfilter_vf_gradfun_inputs, .outputs = avfilter_vf_gradfun_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_histeq.c b/libavfilter/vf_histeq.c index 33cddaadbe16f3474d6cba179eb1fd6401bdf2ee..a4166c674f85a387dce01d750e4d2fcf27653eda 100644 --- a/libavfilter/vf_histeq.c +++ b/libavfilter/vf_histeq.c @@ -279,4 +279,5 @@ AVFilter avfilter_vf_histeq = { .inputs = histeq_inputs, .outputs = histeq_outputs, .priv_class = &histeq_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_hqdn3d.c b/libavfilter/vf_hqdn3d.c index a722d8f131033798dd8c5d35729d8f9777b9ead0..003f1756dfe4da3bdf276f7a1cee9e1fee062047 100644 --- a/libavfilter/vf_hqdn3d.c +++ b/libavfilter/vf_hqdn3d.c @@ -355,6 +355,6 @@ AVFilter avfilter_vf_hqdn3d = { .query_formats = query_formats, .inputs = avfilter_vf_hqdn3d_inputs, - .outputs = avfilter_vf_hqdn3d_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_hue.c b/libavfilter/vf_hue.c index a1280bef13d3642138db941a64486e4600c31e99..9f632405ce0e15ceb21e6e1b9a0443c0e0d02282 100644 --- a/libavfilter/vf_hue.c +++ b/libavfilter/vf_hue.c @@ -349,4 +349,5 @@ AVFilter avfilter_vf_hue = { .inputs = hue_inputs, .outputs = hue_outputs, .priv_class = &hue_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_lut.c b/libavfilter/vf_lut.c index 4c0cdfc29ca8f65dda506fd5b527a55b07ba52c0..4313e772046f8077b586b5a60556304bc0e935e1 100644 --- a/libavfilter/vf_lut.c +++ b/libavfilter/vf_lut.c @@ -350,6 +350,7 @@ static const AVFilterPad outputs[] = { \ .inputs = inputs, \ .outputs = outputs, \ + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, \ } #if CONFIG_LUT_FILTER diff --git a/libavfilter/vf_noise.c b/libavfilter/vf_noise.c index ae64b7696162c85f0122a9b03c8c4fcd4e7074f1..996311a661a201bc53c4768476349da9113bf0d0 100644 --- a/libavfilter/vf_noise.c +++ b/libavfilter/vf_noise.c @@ -471,4 +471,5 @@ AVFilter avfilter_vf_noise = { .inputs = noise_inputs, .outputs = noise_outputs, .priv_class = &noise_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_pp.c b/libavfilter/vf_pp.c index d2c1965a13b307a3137c77099d5a6fe8e250170b..0571cfc1e734a3c7042047196b768b4f587ab750 100644 --- a/libavfilter/vf_pp.c +++ b/libavfilter/vf_pp.c @@ -180,5 +180,5 @@ AVFilter avfilter_vf_pp = { .outputs = pp_outputs, .process_command = pp_process_command, .priv_class = &pp_class, - + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_smartblur.c b/libavfilter/vf_smartblur.c index aed3fa6e83b115af2eeba19e5551bba5c5b8ed5c..4dd1664676a8abf568d2f138625adf9b91a10107 100644 --- a/libavfilter/vf_smartblur.c +++ b/libavfilter/vf_smartblur.c @@ -301,4 +301,5 @@ AVFilter avfilter_vf_smartblur = { .inputs = smartblur_inputs, .outputs = smartblur_outputs, .priv_class = &smartblur_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, }; diff --git a/libavfilter/vf_unsharp.c b/libavfilter/vf_unsharp.c index 038ba4bafe492460f7bf3c1c071f770b46312132..2d7aab26aa2eac4fa5116b33844c95b67f31e58c 100644 --- a/libavfilter/vf_unsharp.c +++ b/libavfilter/vf_unsharp.c @@ -299,6 +299,6 @@ AVFilter avfilter_vf_unsharp = { .query_formats = query_formats, .inputs = avfilter_vf_unsharp_inputs, - .outputs = avfilter_vf_unsharp_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, };