Flag added in a few simple filters. A bunch of other filters can likely use the feature as well.
Unified · Split
| @@ -30,6 +30,7 @@ version <next>: · 29 unmodified lines | ||
| 30 | 30 | - decent native animated GIF encoding |
| 31 | 31 | - asetrate filter |
| 32 | 32 | - interleave filter |
| 33 | +- timeline editing with filters | |
| 33 | 34 | |
| 34 | 35 | |
| 35 | 36 | version 1.2: |
| @@ -1683,6 +1683,8 @@ static void show_help_filter(const char *name) · 1682 unmodified lines | ||
| 1683 | 1683 | if (f->priv_class) |
| 1684 | 1684 | show_help_children(f->priv_class, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | |
| 1685 | 1685 | AV_OPT_FLAG_AUDIO_PARAM); |
| 1686 | + if (f->flags & AVFILTER_FLAG_SUPPORT_TIMELINE) | |
| 1687 | + printf("This filter has support for timeline through the 'enable' option.\n"); | |
| 1686 | 1688 | #else |
| 1687 | 1689 | av_log(NULL, AV_LOG_ERROR, "Build without libavfilter; " |
| 1688 | 1690 | "can not to satisfy request\n"); |
| @@ -267,6 +267,36 @@ See the ``Quoting and escaping'' section in the ffmpeg-utils manual · 266 unmodified lines | ||
| 267 | 267 | for more information about the escaping and quoting rules adopted by |
| 268 | 268 | FFmpeg. |
| 269 | 269 | |
| 270 | +@chapter Timeline editing | |
| 271 | + | |
| 272 | +Some filters support a generic @option{enable} option. For the filters | |
| 273 | +supporting timeline editing, this option can be set to an expression which is | |
| 274 | +evaluated before sending a frame to the filter. If the evaluation is non-zero, | |
| 275 | +the filter will be enabled, otherwise the frame will be sent unchanged to the | |
| 276 | +next filter in the filtergraph. | |
| 277 | + | |
| 278 | +The expression accepts the following values: | |
| 279 | +@table @samp | |
| 280 | +@item t | |
| 281 | +timestamp expressed in seconds, NAN if the input timestamp is unknown | |
| 282 | + | |
| 283 | +@item n | |
| 284 | +sequential number of the input frame, starting from 0 | |
| 285 | + | |
| 286 | +@item pos | |
| 287 | +the position in the file of the input frame, NAN if unknown | |
| 288 | +@end table | |
| 289 | + | |
| 290 | +Like any other filtering option, the @option{enable} option follows the same | |
| 291 | +rules. | |
| 292 | + | |
| 293 | +For example, to enable a denoiser filter (@ref{hqdn3d}) from 10 seconds to 3 | |
| 294 | +minutes, and a @ref{curves} filter starting at 3 seconds: | |
| 295 | +@example | |
| 296 | +hqdn3d = enable='between(t,10,3*60)', | |
| 297 | +curves = enable='gte(t,3)' : preset=cross_process | |
| 298 | +@end example | |
| 299 | + | |
| 270 | 300 | @c man end FILTERGRAPH DESCRIPTION |
| 271 | 301 | |
| 272 | 302 | @chapter Audio Filters |
| @@ -2409,6 +2439,7 @@ indicates never reset and return the largest area encountered during · 2136 unmodified lines | ||
| 2409 | 2439 | playback. |
| 2410 | 2440 | @end table |
| 2411 | 2441 | |
| 2442 | +@anchor{curves} | |
| 2412 | 2443 | @section curves |
| 2413 | 2444 | |
| 2414 | 2445 | Apply color adjustments using curves. |
| @@ -4013,6 +4044,7 @@ ffplay -i input -vf histogram · 1598 unmodified lines | ||
| 4013 | 4044 | |
| 4014 | 4045 | @end itemize |
| 4015 | 4046 | |
| 4047 | +@anchor{hqdn3d} | |
| 4016 | 4048 | @section hqdn3d |
| 4017 | 4049 | |
| 4018 | 4050 | High precision/quality 3d denoise filter. This filter aims to reduce |
| @@ -296,4 +296,5 @@ AVFilter avfilter_af_volume = { · 295 unmodified lines | ||
| 296 | 296 | .init = init, |
| 297 | 297 | .inputs = avfilter_af_volume_inputs, |
| 298 | 298 | .outputs = avfilter_af_volume_outputs, |
| 299 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 299 | 300 | }; |
| @@ -23,6 +23,7 @@ · 22 unmodified lines | ||
| 23 | 23 | #include "libavutil/avstring.h" |
| 24 | 24 | #include "libavutil/channel_layout.h" |
| 25 | 25 | #include "libavutil/common.h" |
| 26 | +#include "libavutil/eval.h" | |
| 26 | 27 | #include "libavutil/imgutils.h" |
| 27 | 28 | #include "libavutil/opt.h" |
| 28 | 29 | #include "libavutil/pixdesc.h" |
| @@ -483,12 +484,20 @@ static const AVClass *filter_child_class_next(const AVClass *prev) · 454 unmodified lines | ||
| 483 | 484 | return NULL; |
| 484 | 485 | } |
| 485 | 486 | |
| 487 | +#define OFFSET(x) offsetof(AVFilterContext, x) | |
| 488 | +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | |
| 489 | +static const AVOption filters_common_options[] = { | |
| 490 | + { "enable", "set enable expression", OFFSET(enable_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
| 491 | + { NULL } | |
| 492 | +}; | |
| 493 | + | |
| 486 | 494 | static const AVClass avfilter_class = { |
| 487 | 495 | .class_name = "AVFilter", |
| 488 | 496 | .item_name = default_filter_name, |
| 489 | 497 | .version = LIBAVUTIL_VERSION_INT, |
| 490 | 498 | .category = AV_CLASS_CATEGORY_FILTER, |
| 491 | 499 | .child_next = filter_child_next, |
| 500 | + .option = filters_common_options, | |
| 492 | 501 | .child_class_next = filter_child_class_next, |
| 493 | 502 | }; |
| 494 | 503 | |
| @@ -618,9 +627,16 @@ void avfilter_free(AVFilterContext *filter) · 123 unmodified lines | ||
| 618 | 627 | while(filter->command_queue){ |
| 619 | 628 | ff_command_queue_pop(filter); |
| 620 | 629 | } |
| 630 | + av_opt_free(filter); | |
| 631 | + av_expr_free(filter->enable); | |
| 632 | + filter->enable = NULL; | |
| 633 | + av_freep(&filter->var_values); | |
| 621 | 634 | av_free(filter); |
| 622 | 635 | } |
| 623 | 636 | |
| 637 | +static const char *const var_names[] = { "t", "n", "pos", NULL }; | |
| 638 | +enum { VAR_T, VAR_N, VAR_POS, VAR_VARS_NB }; | |
| 639 | + | |
| 624 | 640 | static int process_options(AVFilterContext *ctx, AVDictionary **options, |
| 625 | 641 | const char *args) |
| 626 | 642 | { |
| @@ -630,6 +646,8 @@ static int process_options(AVFilterContext *ctx, AVDictionary **options, · 3 unmodified lines | ||
| 630 | 646 | const char *key; |
| 631 | 647 | int offset= -1; |
| 632 | 648 | |
| 649 | + av_opt_set_defaults(ctx); | |
| 650 | + | |
| 633 | 651 | if (!args) |
| 634 | 652 | return 0; |
| 635 | 653 | |
| @@ -665,6 +683,12 @@ static int process_options(AVFilterContext *ctx, AVDictionary **options, · 29 unmodified lines | ||
| 665 | 683 | } |
| 666 | 684 | |
| 667 | 685 | av_log(ctx, AV_LOG_DEBUG, "Setting '%s' to value '%s'\n", key, value); |
| 686 | + | |
| 687 | + if (av_opt_find(ctx, key, NULL, 0, 0)) { | |
| 688 | + ret = av_opt_set(ctx, key, value, 0); | |
| 689 | + if (ret < 0) | |
| 690 | + return ret; | |
| 691 | + } else { | |
| 668 | 692 | av_dict_set(options, key, value, 0); |
| 669 | 693 | if ((ret = av_opt_set(ctx->priv, key, value, 0)) < 0) { |
| 670 | 694 | 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, · 4 unmodified lines | ||
| 675 | 699 | return ret; |
| 676 | 700 | } |
| 677 | 701 | } |
| 702 | + } | |
| 678 | 703 | |
| 679 | 704 | av_free(value); |
| 680 | 705 | av_free(parsed_key); |
| 681 | 706 | count++; |
| 682 | 707 | } |
| 708 | + | |
| 709 | + if (ctx->enable_str) { | |
| 710 | + if (!(ctx->filter->flags & AVFILTER_FLAG_SUPPORT_TIMELINE)) { | |
| 711 | + av_log(ctx, AV_LOG_ERROR, "Timeline ('enable' option) not supported " | |
| 712 | + "with filter '%s'\n", ctx->filter->name); | |
| 713 | + return AVERROR_PATCHWELCOME; | |
| 714 | + } | |
| 715 | + ctx->var_values = av_calloc(VAR_VARS_NB, sizeof(*ctx->var_values)); | |
| 716 | + if (!ctx->var_values) | |
| 717 | + return AVERROR(ENOMEM); | |
| 718 | + ret = av_expr_parse((AVExpr**)&ctx->enable, ctx->enable_str, var_names, | |
| 719 | + NULL, NULL, NULL, NULL, 0, ctx->priv); | |
| 720 | + if (ret < 0) | |
| 721 | + return ret; | |
| 722 | + } | |
| 683 | 723 | return count; |
| 684 | 724 | } |
| 685 | 725 | |
| @@ -852,6 +892,7 @@ static int default_filter_frame(AVFilterLink *link, AVFrame *frame) · 166 unmodified lines | ||
| 852 | 892 | static int ff_filter_frame_framed(AVFilterLink *link, AVFrame *frame) |
| 853 | 893 | { |
| 854 | 894 | int (*filter_frame)(AVFilterLink *, AVFrame *); |
| 895 | + AVFilterContext *dstctx = link->dst; | |
| 855 | 896 | AVFilterPad *dst = link->dstpad; |
| 856 | 897 | AVFrame *out; |
| 857 | 898 | int ret; |
| @@ -914,6 +955,15 @@ static int ff_filter_frame_framed(AVFilterLink *link, AVFrame *frame) · 56 unmodified lines | ||
| 914 | 955 | } |
| 915 | 956 | |
| 916 | 957 | pts = out->pts; |
| 958 | + if (dstctx->enable_str) { | |
| 959 | + int64_t pos = av_frame_get_pkt_pos(out); | |
| 960 | + dstctx->var_values[VAR_N] = link->frame_count; | |
| 961 | + dstctx->var_values[VAR_T] = pts == AV_NOPTS_VALUE ? NAN : pts * av_q2d(link->time_base); | |
| 962 | + dstctx->var_values[VAR_POS] = pos == -1 ? NAN : pos; | |
| 963 | + if (!av_expr_eval(dstctx->enable, dstctx->var_values, NULL)) | |
| 964 | + filter_frame = dst->passthrough_filter_frame ? dst->passthrough_filter_frame | |
| 965 | + : default_filter_frame; | |
| 966 | + } | |
| 917 | 967 | ret = filter_frame(link, out); |
| 918 | 968 | link->frame_count++; |
| 919 | 969 | link->frame_requested = 0; |
| @@ -385,6 +385,19 @@ struct AVFilterPad { · 384 unmodified lines | ||
| 385 | 385 | int needs_fifo; |
| 386 | 386 | |
| 387 | 387 | int needs_writable; |
| 388 | + | |
| 389 | + /** | |
| 390 | + * Passthrough filtering callback. | |
| 391 | + * | |
| 392 | + * If a filter supports timeline editing (in case | |
| 393 | + * AVFILTER_FLAG_SUPPORT_TIMELINE is enabled) then it can implement a | |
| 394 | + * custom passthrough callback to update its local context (for example to | |
| 395 | + * keep a frame reference, or simply send the filter to a custom outlink). | |
| 396 | + * The filter must not do any change to the frame in this callback. | |
| 397 | + * | |
| 398 | + * Input pads only. | |
| 399 | + */ | |
| 400 | + int (*passthrough_filter_frame)(AVFilterLink *link, AVFrame *frame); | |
| 388 | 401 | }; |
| 389 | 402 | #endif |
| 390 | 403 | |
| @@ -428,6 +441,12 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx); · 37 unmodified lines | ||
| 428 | 441 | * the options supplied to it. |
| 429 | 442 | */ |
| 430 | 443 | #define AVFILTER_FLAG_DYNAMIC_OUTPUTS (1 << 1) |
| 444 | +/** | |
| 445 | + * Some filters support a generic "enable" expression option that can be used | |
| 446 | + * to enable or disable a filter in the timeline. Filters supporting this | |
| 447 | + * option have this flag set. | |
| 448 | + */ | |
| 449 | +#define AVFILTER_FLAG_SUPPORT_TIMELINE (1 << 16) | |
| 431 | 450 | |
| 432 | 451 | /** |
| 433 | 452 | * Filter definition. This defines the pads a filter contains, and all the |
| @@ -522,7 +541,7 @@ typedef struct AVFilter { · 88 unmodified lines | ||
| 522 | 541 | |
| 523 | 542 | /** An instance of a filter */ |
| 524 | 543 | struct AVFilterContext { |
| 525 | − const AVClass *av_class; ///< needed for av_log() | |
| 544 | + const AVClass *av_class; ///< needed for av_log() and filters common options | |
| 526 | 545 | |
| 527 | 546 | const AVFilter *filter; ///< the AVFilter of which this is an instance |
| 528 | 547 | |
| @@ -547,6 +566,10 @@ struct AVFilterContext { · 18 unmodified lines | ||
| 547 | 566 | struct AVFilterGraph *graph; ///< filtergraph this filter belongs to |
| 548 | 567 | |
| 549 | 568 | struct AVFilterCommand *command_queue; |
| 569 | + | |
| 570 | + char *enable_str; ///< enable expression string | |
| 571 | + void *enable; ///< parsed expression (AVExpr*) | |
| 572 | + double *var_values; ///< variable values for the enable expression | |
| 550 | 573 | }; |
| 551 | 574 | |
| 552 | 575 | /** |
| @@ -383,4 +383,5 @@ AVFilter avfilter_vf_boxblur = { · 382 unmodified lines | ||
| 383 | 383 | |
| 384 | 384 | .inputs = avfilter_vf_boxblur_inputs, |
| 385 | 385 | .outputs = avfilter_vf_boxblur_outputs, |
| 386 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 386 | 387 | }; |
| @@ -385,4 +385,5 @@ AVFilter avfilter_vf_colormatrix = { · 384 unmodified lines | ||
| 385 | 385 | .inputs = colormatrix_inputs, |
| 386 | 386 | .outputs = colormatrix_outputs, |
| 387 | 387 | .priv_class = &colormatrix_class, |
| 388 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 388 | 389 | }; |
| @@ -233,4 +233,5 @@ AVFilter avfilter_vf_cropdetect = { · 232 unmodified lines | ||
| 233 | 233 | .query_formats = query_formats, |
| 234 | 234 | .inputs = avfilter_vf_cropdetect_inputs, |
| 235 | 235 | .outputs = avfilter_vf_cropdetect_outputs, |
| 236 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 236 | 237 | }; |
| @@ -514,4 +514,5 @@ AVFilter avfilter_vf_curves = { · 513 unmodified lines | ||
| 514 | 514 | .inputs = curves_inputs, |
| 515 | 515 | .outputs = curves_outputs, |
| 516 | 516 | .priv_class = &curves_class, |
| 517 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 517 | 518 | }; |
| @@ -181,4 +181,5 @@ AVFilter avfilter_vf_drawbox = { · 180 unmodified lines | ||
| 181 | 181 | .query_formats = query_formats, |
| 182 | 182 | .inputs = avfilter_vf_drawbox_inputs, |
| 183 | 183 | .outputs = avfilter_vf_drawbox_outputs, |
| 184 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 184 | 185 | }; |
| @@ -327,4 +327,5 @@ AVFilter avfilter_vf_edgedetect = { · 326 unmodified lines | ||
| 327 | 327 | .inputs = edgedetect_inputs, |
| 328 | 328 | .outputs = edgedetect_outputs, |
| 329 | 329 | .priv_class = &edgedetect_class, |
| 330 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 330 | 331 | }; |
| @@ -260,4 +260,5 @@ AVFilter avfilter_vf_gradfun = { · 259 unmodified lines | ||
| 260 | 260 | .query_formats = query_formats, |
| 261 | 261 | .inputs = avfilter_vf_gradfun_inputs, |
| 262 | 262 | .outputs = avfilter_vf_gradfun_outputs, |
| 263 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 263 | 264 | }; |
| @@ -279,4 +279,5 @@ AVFilter avfilter_vf_histeq = { · 278 unmodified lines | ||
| 279 | 279 | .inputs = histeq_inputs, |
| 280 | 280 | .outputs = histeq_outputs, |
| 281 | 281 | .priv_class = &histeq_class, |
| 282 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 282 | 283 | }; |
| @@ -355,6 +355,6 @@ AVFilter avfilter_vf_hqdn3d = { · 354 unmodified lines | ||
| 355 | 355 | .query_formats = query_formats, |
| 356 | 356 | |
| 357 | 357 | .inputs = avfilter_vf_hqdn3d_inputs, |
| 358 | − | |
| 359 | 358 | .outputs = avfilter_vf_hqdn3d_outputs, |
| 359 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 360 | 360 | }; |
| @@ -349,4 +349,5 @@ AVFilter avfilter_vf_hue = { · 348 unmodified lines | ||
| 349 | 349 | .inputs = hue_inputs, |
| 350 | 350 | .outputs = hue_outputs, |
| 351 | 351 | .priv_class = &hue_class, |
| 352 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 352 | 353 | }; |
| @@ -350,6 +350,7 @@ static const AVFilterPad outputs[] = { · 349 unmodified lines | ||
| 350 | 350 | \ |
| 351 | 351 | .inputs = inputs, \ |
| 352 | 352 | .outputs = outputs, \ |
| 353 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, \ | |
| 353 | 354 | } |
| 354 | 355 | |
| 355 | 356 | #if CONFIG_LUT_FILTER |
| @@ -471,4 +471,5 @@ AVFilter avfilter_vf_noise = { · 470 unmodified lines | ||
| 471 | 471 | .inputs = noise_inputs, |
| 472 | 472 | .outputs = noise_outputs, |
| 473 | 473 | .priv_class = &noise_class, |
| 474 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 474 | 475 | }; |
| @@ -180,5 +180,5 @@ AVFilter avfilter_vf_pp = { · 179 unmodified lines | ||
| 180 | 180 | .outputs = pp_outputs, |
| 181 | 181 | .process_command = pp_process_command, |
| 182 | 182 | .priv_class = &pp_class, |
| 183 | − | |
| 183 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 184 | 184 | }; |
| @@ -301,4 +301,5 @@ AVFilter avfilter_vf_smartblur = { · 300 unmodified lines | ||
| 301 | 301 | .inputs = smartblur_inputs, |
| 302 | 302 | .outputs = smartblur_outputs, |
| 303 | 303 | .priv_class = &smartblur_class, |
| 304 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 304 | 305 | }; |
| @@ -299,6 +299,6 @@ AVFilter avfilter_vf_unsharp = { · 298 unmodified lines | ||
| 299 | 299 | .query_formats = query_formats, |
| 300 | 300 | |
| 301 | 301 | .inputs = avfilter_vf_unsharp_inputs, |
| 302 | − | |
| 303 | 302 | .outputs = avfilter_vf_unsharp_outputs, |
| 303 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE, | |
| 304 | 304 | }; |