Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (c) 2016 Paul B Mahol | ||
3 | * | ||
4 | * This file is part of FFmpeg. | ||
5 | * | ||
6 | * FFmpeg is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU Lesser General Public | ||
8 | * License as published by the Free Software Foundation; either | ||
9 | * version 2.1 of the License, or (at your option) any later version. | ||
10 | * | ||
11 | * FFmpeg is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * Lesser General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU Lesser General Public | ||
17 | * License along with FFmpeg; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file | ||
23 | * filter for manipulating frame metadata | ||
24 | */ | ||
25 | |||
26 | #include "config_components.h" | ||
27 | |||
28 | #include <float.h> | ||
29 | |||
30 | #include "libavutil/avassert.h" | ||
31 | #include "libavutil/avstring.h" | ||
32 | #include "libavutil/eval.h" | ||
33 | #include "libavutil/internal.h" | ||
34 | #include "libavutil/opt.h" | ||
35 | #include "libavutil/timestamp.h" | ||
36 | #include "libavformat/avio.h" | ||
37 | #include "avfilter.h" | ||
38 | #include "audio.h" | ||
39 | #include "filters.h" | ||
40 | #include "video.h" | ||
41 | |||
42 | enum MetadataMode { | ||
43 | METADATA_SELECT, | ||
44 | METADATA_ADD, | ||
45 | METADATA_MODIFY, | ||
46 | METADATA_DELETE, | ||
47 | METADATA_PRINT, | ||
48 | METADATA_NB | ||
49 | }; | ||
50 | |||
51 | enum MetadataFunction { | ||
52 | METADATAF_SAME_STR, | ||
53 | METADATAF_STARTS_WITH, | ||
54 | METADATAF_LESS, | ||
55 | METADATAF_EQUAL, | ||
56 | METADATAF_GREATER, | ||
57 | METADATAF_EXPR, | ||
58 | METADATAF_ENDS_WITH, | ||
59 | METADATAF_NB | ||
60 | }; | ||
61 | |||
62 | static const char *const var_names[] = { | ||
63 | "VALUE1", | ||
64 | "VALUE2", | ||
65 | "FRAMEVAL", | ||
66 | "USERVAL", | ||
67 | NULL | ||
68 | }; | ||
69 | |||
70 | enum var_name { | ||
71 | VAR_VALUE1, | ||
72 | VAR_VALUE2, | ||
73 | VAR_FRAMEVAL, | ||
74 | VAR_USERVAL, | ||
75 | VAR_VARS_NB | ||
76 | }; | ||
77 | |||
78 | typedef struct MetadataContext { | ||
79 | const AVClass *class; | ||
80 | |||
81 | int mode; | ||
82 | char *key; | ||
83 | char *value; | ||
84 | int function; | ||
85 | |||
86 | char *expr_str; | ||
87 | AVExpr *expr; | ||
88 | double var_values[VAR_VARS_NB]; | ||
89 | |||
90 | AVIOContext* avio_context; | ||
91 | char *file_str; | ||
92 | |||
93 | int (*compare)(struct MetadataContext *s, | ||
94 | const char *value1, const char *value2); | ||
95 | void (*print)(AVFilterContext *ctx, const char *msg, ...) av_printf_format(2, 3); | ||
96 | |||
97 | int direct; // reduces buffering when printing to user-supplied URL | ||
98 | } MetadataContext; | ||
99 | |||
100 | #define OFFSET(x) offsetof(MetadataContext, x) | ||
101 | #define DEFINE_OPTIONS(filt_name, FLAGS) \ | ||
102 | static const AVOption filt_name##_options[] = { \ | ||
103 | { "mode", "set a mode of operation", OFFSET(mode), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, METADATA_NB-1, FLAGS, .unit = "mode" }, \ | ||
104 | { "select", "select frame", 0, AV_OPT_TYPE_CONST, {.i64 = METADATA_SELECT }, 0, 0, FLAGS, .unit = "mode" }, \ | ||
105 | { "add", "add new metadata", 0, AV_OPT_TYPE_CONST, {.i64 = METADATA_ADD }, 0, 0, FLAGS, .unit = "mode" }, \ | ||
106 | { "modify", "modify metadata", 0, AV_OPT_TYPE_CONST, {.i64 = METADATA_MODIFY }, 0, 0, FLAGS, .unit = "mode" }, \ | ||
107 | { "delete", "delete metadata", 0, AV_OPT_TYPE_CONST, {.i64 = METADATA_DELETE }, 0, 0, FLAGS, .unit = "mode" }, \ | ||
108 | { "print", "print metadata", 0, AV_OPT_TYPE_CONST, {.i64 = METADATA_PRINT }, 0, 0, FLAGS, .unit = "mode" }, \ | ||
109 | { "key", "set metadata key", OFFSET(key), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, FLAGS }, \ | ||
110 | { "value", "set metadata value", OFFSET(value), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, FLAGS }, \ | ||
111 | { "function", "function for comparing values", OFFSET(function), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, METADATAF_NB-1, FLAGS, .unit = "function" }, \ | ||
112 | { "same_str", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_SAME_STR }, 0, 3, FLAGS, .unit = "function" }, \ | ||
113 | { "starts_with", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_STARTS_WITH }, 0, 0, FLAGS, .unit = "function" }, \ | ||
114 | { "less", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_LESS }, 0, 3, FLAGS, .unit = "function" }, \ | ||
115 | { "equal", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_EQUAL }, 0, 3, FLAGS, .unit = "function" }, \ | ||
116 | { "greater", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_GREATER }, 0, 3, FLAGS, .unit = "function" }, \ | ||
117 | { "expr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_EXPR }, 0, 3, FLAGS, .unit = "function" }, \ | ||
118 | { "ends_with", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_ENDS_WITH }, 0, 0, FLAGS, .unit = "function" }, \ | ||
119 | { "expr", "set expression for expr function", OFFSET(expr_str), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, FLAGS }, \ | ||
120 | { "file", "set file where to print metadata information", OFFSET(file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, \ | ||
121 | { "direct", "reduce buffering when printing to user-set file or pipe", OFFSET(direct), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS }, \ | ||
122 | { NULL } \ | ||
123 | } | ||
124 | |||
125 | ✗ | static int same_str(MetadataContext *s, const char *value1, const char *value2) | |
126 | { | ||
127 | ✗ | return !strcmp(value1, value2); | |
128 | } | ||
129 | |||
130 | ✗ | static int starts_with(MetadataContext *s, const char *value1, const char *value2) | |
131 | { | ||
132 | ✗ | return !strncmp(value1, value2, strlen(value2)); | |
133 | } | ||
134 | |||
135 | ✗ | static int ends_with(MetadataContext *s, const char *value1, const char *value2) | |
136 | { | ||
137 | ✗ | const int len1 = strlen(value1); | |
138 | ✗ | const int len2 = strlen(value2); | |
139 | |||
140 | ✗ | return !strncmp(value1 + FFMAX(len1 - len2, 0), value2, len2); | |
141 | } | ||
142 | |||
143 | ✗ | static int equal(MetadataContext *s, const char *value1, const char *value2) | |
144 | { | ||
145 | float f1, f2; | ||
146 | |||
147 | ✗ | if (sscanf(value1, "%f", &f1) + sscanf(value2, "%f", &f2) != 2) | |
148 | ✗ | return 0; | |
149 | |||
150 | ✗ | return fabsf(f1 - f2) < FLT_EPSILON; | |
151 | } | ||
152 | |||
153 | ✗ | static int less(MetadataContext *s, const char *value1, const char *value2) | |
154 | { | ||
155 | float f1, f2; | ||
156 | |||
157 | ✗ | if (sscanf(value1, "%f", &f1) + sscanf(value2, "%f", &f2) != 2) | |
158 | ✗ | return 0; | |
159 | |||
160 | ✗ | return (f1 - f2) < FLT_EPSILON; | |
161 | } | ||
162 | |||
163 | ✗ | static int greater(MetadataContext *s, const char *value1, const char *value2) | |
164 | { | ||
165 | float f1, f2; | ||
166 | |||
167 | ✗ | if (sscanf(value1, "%f", &f1) + sscanf(value2, "%f", &f2) != 2) | |
168 | ✗ | return 0; | |
169 | |||
170 | ✗ | return (f2 - f1) < FLT_EPSILON; | |
171 | } | ||
172 | |||
173 | ✗ | static int parse_expr(MetadataContext *s, const char *value1, const char *value2) | |
174 | { | ||
175 | double f1, f2; | ||
176 | |||
177 | ✗ | if (sscanf(value1, "%lf", &f1) + sscanf(value2, "%lf", &f2) != 2) | |
178 | ✗ | return 0; | |
179 | |||
180 | ✗ | s->var_values[VAR_VALUE1] = s->var_values[VAR_FRAMEVAL] = f1; | |
181 | ✗ | s->var_values[VAR_VALUE2] = s->var_values[VAR_USERVAL] = f2; | |
182 | |||
183 | ✗ | return av_expr_eval(s->expr, s->var_values, NULL); | |
184 | } | ||
185 | |||
186 | 140 | static void print_log(AVFilterContext *ctx, const char *msg, ...) | |
187 | { | ||
188 | va_list argument_list; | ||
189 | |||
190 | 140 | va_start(argument_list, msg); | |
191 |
1/2✓ Branch 0 taken 140 times.
✗ Branch 1 not taken.
|
140 | if (msg) |
192 | 140 | av_vlog(ctx, AV_LOG_INFO, msg, argument_list); | |
193 | 140 | va_end(argument_list); | |
194 | 140 | } | |
195 | |||
196 | 267 | static void print_file(AVFilterContext *ctx, const char *msg, ...) | |
197 | { | ||
198 | 267 | MetadataContext *s = ctx->priv; | |
199 | va_list argument_list; | ||
200 | |||
201 | 267 | va_start(argument_list, msg); | |
202 |
1/2✓ Branch 0 taken 267 times.
✗ Branch 1 not taken.
|
267 | if (msg) { |
203 | char buf[128]; | ||
204 | 267 | vsnprintf(buf, sizeof(buf), msg, argument_list); | |
205 | 267 | avio_write(s->avio_context, buf, av_strnlen(buf, sizeof(buf))); | |
206 | } | ||
207 | 267 | va_end(argument_list); | |
208 | 267 | } | |
209 | |||
210 | 28 | static av_cold int init(AVFilterContext *ctx) | |
211 | { | ||
212 | 28 | MetadataContext *s = ctx->priv; | |
213 | int ret; | ||
214 | |||
215 |
2/6✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
28 | if (!s->key && s->mode != METADATA_PRINT && s->mode != METADATA_DELETE) { |
216 | ✗ | av_log(ctx, AV_LOG_WARNING, "Metadata key must be set\n"); | |
217 | ✗ | return AVERROR(EINVAL); | |
218 | } | ||
219 | |||
220 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | if ((s->mode == METADATA_MODIFY || |
221 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
28 | s->mode == METADATA_ADD) && !s->value) { |
222 | ✗ | av_log(ctx, AV_LOG_WARNING, "Missing metadata value\n"); | |
223 | ✗ | return AVERROR(EINVAL); | |
224 | } | ||
225 | |||
226 |
1/8✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
28 | switch (s->function) { |
227 | 28 | case METADATAF_SAME_STR: | |
228 | 28 | s->compare = same_str; | |
229 | 28 | break; | |
230 | ✗ | case METADATAF_STARTS_WITH: | |
231 | ✗ | s->compare = starts_with; | |
232 | ✗ | break; | |
233 | ✗ | case METADATAF_ENDS_WITH: | |
234 | ✗ | s->compare = ends_with; | |
235 | ✗ | break; | |
236 | ✗ | case METADATAF_LESS: | |
237 | ✗ | s->compare = less; | |
238 | ✗ | break; | |
239 | ✗ | case METADATAF_EQUAL: | |
240 | ✗ | s->compare = equal; | |
241 | ✗ | break; | |
242 | ✗ | case METADATAF_GREATER: | |
243 | ✗ | s->compare = greater; | |
244 | ✗ | break; | |
245 | ✗ | case METADATAF_EXPR: | |
246 | ✗ | s->compare = parse_expr; | |
247 | ✗ | break; | |
248 | ✗ | default: | |
249 | ✗ | av_assert0(0); | |
250 | }; | ||
251 | |||
252 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
|
28 | if (s->function == METADATAF_EXPR) { |
253 | ✗ | if (!s->expr_str) { | |
254 | ✗ | av_log(ctx, AV_LOG_WARNING, "expr option not set\n"); | |
255 | ✗ | return AVERROR(EINVAL); | |
256 | } | ||
257 | ✗ | if ((ret = av_expr_parse(&s->expr, s->expr_str, | |
258 | var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { | ||
259 | ✗ | av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", s->expr_str); | |
260 | ✗ | return ret; | |
261 | } | ||
262 | } | ||
263 | |||
264 |
3/4✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 2 times.
|
28 | if (s->mode == METADATA_PRINT && s->file_str) { |
265 | 26 | s->print = print_file; | |
266 | } else { | ||
267 | 2 | s->print = print_log; | |
268 | } | ||
269 | |||
270 | 28 | s->avio_context = NULL; | |
271 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 2 times.
|
28 | if (s->file_str) { |
272 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | if (!strcmp("-", s->file_str)) { |
273 | 26 | ret = avio_open(&s->avio_context, "pipe:1", AVIO_FLAG_WRITE); | |
274 | } else { | ||
275 | ✗ | ret = avio_open(&s->avio_context, s->file_str, AVIO_FLAG_WRITE); | |
276 | } | ||
277 | |||
278 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
|
26 | if (ret < 0) { |
279 | ✗ | av_log(ctx, AV_LOG_ERROR, "Could not open %s: %s\n", | |
280 | ✗ | s->file_str, av_err2str(ret)); | |
281 | ✗ | return ret; | |
282 | } | ||
283 | |||
284 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
|
26 | if (s->direct) |
285 | ✗ | s->avio_context->direct = AVIO_FLAG_DIRECT; | |
286 | } | ||
287 | |||
288 | 28 | return 0; | |
289 | } | ||
290 | |||
291 | 28 | static av_cold void uninit(AVFilterContext *ctx) | |
292 | { | ||
293 | 28 | MetadataContext *s = ctx->priv; | |
294 | |||
295 | 28 | av_expr_free(s->expr); | |
296 | 28 | s->expr = NULL; | |
297 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 2 times.
|
28 | if (s->avio_context) { |
298 | 26 | avio_closep(&s->avio_context); | |
299 | } | ||
300 | 28 | } | |
301 | |||
302 | 67 | static int filter_frame(AVFilterLink *inlink, AVFrame *frame) | |
303 | { | ||
304 | 67 | FilterLink *inl = ff_filter_link(inlink); | |
305 | 67 | AVFilterContext *ctx = inlink->dst; | |
306 | 67 | AVFilterLink *outlink = ctx->outputs[0]; | |
307 | 67 | MetadataContext *s = ctx->priv; | |
308 | 67 | AVDictionary **metadata = &frame->metadata; | |
309 | const AVDictionaryEntry *e; | ||
310 | |||
311 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
|
67 | e = av_dict_get(*metadata, !s->key ? "" : s->key, NULL, |
312 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | !s->key ? AV_DICT_IGNORE_SUFFIX: 0); |
313 | |||
314 |
1/6✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
67 | switch (s->mode) { |
315 | ✗ | case METADATA_SELECT: | |
316 | ✗ | if (!s->value && e && e->value) { | |
317 | ✗ | return ff_filter_frame(outlink, frame); | |
318 | ✗ | } else if (s->value && e && e->value && | |
319 | ✗ | s->compare(s, e->value, s->value)) { | |
320 | ✗ | return ff_filter_frame(outlink, frame); | |
321 | } | ||
322 | ✗ | break; | |
323 | ✗ | case METADATA_ADD: | |
324 | ✗ | if (e && e->value) { | |
325 | ; | ||
326 | } else { | ||
327 | ✗ | av_dict_set(metadata, s->key, s->value, 0); | |
328 | } | ||
329 | ✗ | return ff_filter_frame(outlink, frame); | |
330 | ✗ | case METADATA_MODIFY: | |
331 | ✗ | if (e && e->value) { | |
332 | ✗ | av_dict_set(metadata, s->key, s->value, 0); | |
333 | } | ||
334 | ✗ | return ff_filter_frame(outlink, frame); | |
335 | 67 | case METADATA_PRINT: | |
336 |
3/4✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✓ Branch 3 taken 4 times.
|
130 | if (!s->key && e) { |
337 | 63 | s->print(ctx, "frame:%-4"PRId64" pts:%-7s pts_time:%s\n", | |
338 | 63 | inl->frame_count_out, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &inlink->time_base)); | |
339 | 63 | s->print(ctx, "%s=%s\n", e->key, e->value); | |
340 |
2/2✓ Branch 1 taken 281 times.
✓ Branch 2 taken 63 times.
|
344 | while ((e = av_dict_iterate(*metadata, e)) != NULL) { |
341 | 281 | s->print(ctx, "%s=%s\n", e->key, e->value); | |
342 | } | ||
343 |
1/10✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
|
4 | } else if (e && e->value && (!s->value || (e->value && s->compare(s, e->value, s->value)))) { |
344 | ✗ | s->print(ctx, "frame:%-4"PRId64" pts:%-7s pts_time:%s\n", | |
345 | ✗ | inl->frame_count_out, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &inlink->time_base)); | |
346 | ✗ | s->print(ctx, "%s=%s\n", s->key, e->value); | |
347 | } | ||
348 | 67 | return ff_filter_frame(outlink, frame); | |
349 | ✗ | case METADATA_DELETE: | |
350 | ✗ | if (!s->key) { | |
351 | ✗ | av_dict_free(metadata); | |
352 | ✗ | } else if (e && e->value && (!s->value || s->compare(s, e->value, s->value))) { | |
353 | ✗ | av_dict_set(metadata, s->key, NULL, 0); | |
354 | } | ||
355 | ✗ | return ff_filter_frame(outlink, frame); | |
356 | ✗ | default: | |
357 | ✗ | av_assert0(0); | |
358 | }; | ||
359 | |||
360 | ✗ | av_frame_free(&frame); | |
361 | |||
362 | ✗ | return 0; | |
363 | } | ||
364 | |||
365 | #if CONFIG_AMETADATA_FILTER | ||
366 | |||
367 | DEFINE_OPTIONS(ametadata, AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM); | ||
368 | AVFILTER_DEFINE_CLASS(ametadata); | ||
369 | |||
370 | static const AVFilterPad ainputs[] = { | ||
371 | { | ||
372 | .name = "default", | ||
373 | .type = AVMEDIA_TYPE_AUDIO, | ||
374 | .filter_frame = filter_frame, | ||
375 | }, | ||
376 | }; | ||
377 | |||
378 | const AVFilter ff_af_ametadata = { | ||
379 | .name = "ametadata", | ||
380 | .description = NULL_IF_CONFIG_SMALL("Manipulate audio frame metadata."), | ||
381 | .priv_size = sizeof(MetadataContext), | ||
382 | .priv_class = &ametadata_class, | ||
383 | .init = init, | ||
384 | .uninit = uninit, | ||
385 | FILTER_INPUTS(ainputs), | ||
386 | FILTER_OUTPUTS(ff_audio_default_filterpad), | ||
387 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | | ||
388 | AVFILTER_FLAG_METADATA_ONLY, | ||
389 | }; | ||
390 | #endif /* CONFIG_AMETADATA_FILTER */ | ||
391 | |||
392 | #if CONFIG_METADATA_FILTER | ||
393 | |||
394 | DEFINE_OPTIONS(metadata, AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM); | ||
395 | AVFILTER_DEFINE_CLASS(metadata); | ||
396 | |||
397 | static const AVFilterPad inputs[] = { | ||
398 | { | ||
399 | .name = "default", | ||
400 | .type = AVMEDIA_TYPE_VIDEO, | ||
401 | .filter_frame = filter_frame, | ||
402 | }, | ||
403 | }; | ||
404 | |||
405 | const AVFilter ff_vf_metadata = { | ||
406 | .name = "metadata", | ||
407 | .description = NULL_IF_CONFIG_SMALL("Manipulate video frame metadata."), | ||
408 | .priv_size = sizeof(MetadataContext), | ||
409 | .priv_class = &metadata_class, | ||
410 | .init = init, | ||
411 | .uninit = uninit, | ||
412 | FILTER_INPUTS(inputs), | ||
413 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
414 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | | ||
415 | AVFILTER_FLAG_METADATA_ONLY, | ||
416 | }; | ||
417 | #endif /* CONFIG_METADATA_FILTER */ | ||
418 |