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,
 };