2 * Copyright (c) 2016 Paul B Mahol
4 * This file is part of FFmpeg.
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.
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.
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
23 * filter for manipulating frame metadata
26 #include "config_components.h"
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"
51 enum MetadataFunction
{
53 METADATAF_STARTS_WITH
,
62 static const char *const var_names
[] = {
78 typedef struct MetadataContext
{
88 double var_values
[VAR_VARS_NB
];
90 AVIOContext
* avio_context
;
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);
97 int direct
; // reduces buffering when printing to user-supplied URL
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 }, \
125 static int same_str(MetadataContext
*s
, const char *value1
, const char *value2
)
127 return !strcmp(value1
, value2
);
130 static int starts_with(MetadataContext
*s
, const char *value1
, const char *value2
)
132 return !strncmp(value1
, value2
, strlen(value2
));
135 static int ends_with(MetadataContext
*s
, const char *value1
, const char *value2
)
137 const int len1
= strlen(value1
);
138 const int len2
= strlen(value2
);
140 return !strncmp(value1
+ FFMAX(len1
- len2
, 0), value2
, len2
);
143 static int equal(MetadataContext
*s
, const char *value1
, const char *value2
)
147 if (sscanf(value1
, "%f", &f1
) + sscanf(value2
, "%f", &f2
) != 2)
150 return fabsf(f1
- f2
) < FLT_EPSILON
;
153 static int less(MetadataContext
*s
, const char *value1
, const char *value2
)
157 if (sscanf(value1
, "%f", &f1
) + sscanf(value2
, "%f", &f2
) != 2)
160 return (f1
- f2
) < FLT_EPSILON
;
163 static int greater(MetadataContext
*s
, const char *value1
, const char *value2
)
167 if (sscanf(value1
, "%f", &f1
) + sscanf(value2
, "%f", &f2
) != 2)
170 return (f2
- f1
) < FLT_EPSILON
;
173 static int parse_expr(MetadataContext
*s
, const char *value1
, const char *value2
)
177 if (sscanf(value1
, "%lf", &f1
) + sscanf(value2
, "%lf", &f2
) != 2)
180 s
->var_values
[VAR_VALUE1
] = s
->var_values
[VAR_FRAMEVAL
] = f1
;
181 s
->var_values
[VAR_VALUE2
] = s
->var_values
[VAR_USERVAL
] = f2
;
183 return av_expr_eval(s
->expr
, s
->var_values
, NULL
);
186 static void print_log(AVFilterContext
*ctx
, const char *msg
, ...)
188 va_list argument_list
;
190 va_start(argument_list
, msg
);
192 av_vlog(ctx
, AV_LOG_INFO
, msg
, argument_list
);
193 va_end(argument_list
);
196 static void print_file(AVFilterContext
*ctx
, const char *msg
, ...)
198 MetadataContext
*s
= ctx
->priv
;
199 va_list argument_list
;
201 va_start(argument_list
, msg
);
204 vsnprintf(buf
, sizeof(buf
), msg
, argument_list
);
205 avio_write(s
->avio_context
, buf
, av_strnlen(buf
, sizeof(buf
)));
207 va_end(argument_list
);
210 static av_cold
int init(AVFilterContext
*ctx
)
212 MetadataContext
*s
= ctx
->priv
;
215 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
);
220 if ((s
->mode
== METADATA_MODIFY
||
221 s
->mode
== METADATA_ADD
) && !s
->value
) {
222 av_log(ctx
, AV_LOG_WARNING
, "Missing metadata value\n");
223 return AVERROR(EINVAL
);
226 switch (s
->function
) {
227 case METADATAF_SAME_STR
:
228 s
->compare
= same_str
;
230 case METADATAF_STARTS_WITH
:
231 s
->compare
= starts_with
;
233 case METADATAF_ENDS_WITH
:
234 s
->compare
= ends_with
;
239 case METADATAF_EQUAL
:
242 case METADATAF_GREATER
:
243 s
->compare
= greater
;
246 s
->compare
= parse_expr
;
252 if (s
->function
== METADATAF_EXPR
) {
254 av_log(ctx
, AV_LOG_WARNING
, "expr option not set\n");
255 return AVERROR(EINVAL
);
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
);
264 if (s
->mode
== METADATA_PRINT
&& s
->file_str
) {
265 s
->print
= print_file
;
267 s
->print
= print_log
;
270 s
->avio_context
= NULL
;
272 if (!strcmp("-", s
->file_str
)) {
273 ret
= avio_open(&s
->avio_context
, "pipe:1", AVIO_FLAG_WRITE
);
275 ret
= avio_open(&s
->avio_context
, s
->file_str
, AVIO_FLAG_WRITE
);
279 av_log(ctx
, AV_LOG_ERROR
, "Could not open %s: %s\n",
280 s
->file_str
, av_err2str(ret
));
285 s
->avio_context
->direct
= AVIO_FLAG_DIRECT
;
291 static av_cold
void uninit(AVFilterContext
*ctx
)
293 MetadataContext
*s
= ctx
->priv
;
295 av_expr_free(s
->expr
);
297 if (s
->avio_context
) {
298 avio_closep(&s
->avio_context
);
302 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*frame
)
304 FilterLink
*inl
= ff_filter_link(inlink
);
305 AVFilterContext
*ctx
= inlink
->dst
;
306 AVFilterLink
*outlink
= ctx
->outputs
[0];
307 MetadataContext
*s
= ctx
->priv
;
308 AVDictionary
**metadata
= &frame
->metadata
;
309 const AVDictionaryEntry
*e
;
311 e
= av_dict_get(*metadata
, !s
->key
? "" : s
->key
, NULL
,
312 !s
->key
? AV_DICT_IGNORE_SUFFIX
: 0);
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
);
327 av_dict_set(metadata
, s
->key
, s
->value
, 0);
329 return ff_filter_frame(outlink
, frame
);
330 case METADATA_MODIFY
:
332 av_dict_set(metadata
, s
->key
, s
->value
, 0);
334 return ff_filter_frame(outlink
, frame
);
337 s
->print(ctx
, "frame:%-4"PRId64
" pts:%-7s pts_time:%s\n",
338 inl
->frame_count_out
, av_ts2str(frame
->pts
), av_ts2timestr(frame
->pts
, &inlink
->time_base
));
339 s
->print(ctx
, "%s=%s\n", e
->key
, e
->value
);
340 while ((e
= av_dict_iterate(*metadata
, e
)) != NULL
) {
341 s
->print(ctx
, "%s=%s\n", e
->key
, e
->value
);
343 } 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
);
348 return ff_filter_frame(outlink
, frame
);
349 case METADATA_DELETE
:
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);
355 return ff_filter_frame(outlink
, frame
);
360 av_frame_free(&frame
);
365 #if CONFIG_AMETADATA_FILTER
367 DEFINE_OPTIONS(ametadata
, AV_OPT_FLAG_AUDIO_PARAM
|AV_OPT_FLAG_FILTERING_PARAM
);
368 AVFILTER_DEFINE_CLASS(ametadata
);
370 static const AVFilterPad ainputs
[] = {
373 .type
= AVMEDIA_TYPE_AUDIO
,
374 .filter_frame
= filter_frame
,
378 const FFFilter ff_af_ametadata
= {
379 .p
.name
= "ametadata",
380 .p
.description
= NULL_IF_CONFIG_SMALL("Manipulate audio frame metadata."),
381 .p
.priv_class
= &ametadata_class
,
382 .p
.flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
|
383 AVFILTER_FLAG_METADATA_ONLY
,
384 .priv_size
= sizeof(MetadataContext
),
387 FILTER_INPUTS(ainputs
),
388 FILTER_OUTPUTS(ff_audio_default_filterpad
),
390 #endif /* CONFIG_AMETADATA_FILTER */
392 #if CONFIG_METADATA_FILTER
394 DEFINE_OPTIONS(metadata
, AV_OPT_FLAG_VIDEO_PARAM
|AV_OPT_FLAG_FILTERING_PARAM
);
395 AVFILTER_DEFINE_CLASS(metadata
);
397 static const AVFilterPad inputs
[] = {
400 .type
= AVMEDIA_TYPE_VIDEO
,
401 .filter_frame
= filter_frame
,
405 const FFFilter ff_vf_metadata
= {
406 .p
.name
= "metadata",
407 .p
.description
= NULL_IF_CONFIG_SMALL("Manipulate video frame metadata."),
408 .p
.priv_class
= &metadata_class
,
409 .p
.flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
|
410 AVFILTER_FLAG_METADATA_ONLY
,
411 .priv_size
= sizeof(MetadataContext
),
414 FILTER_INPUTS(inputs
),
415 FILTER_OUTPUTS(ff_video_default_filterpad
),
417 #endif /* CONFIG_METADATA_FILTER */