• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "formats.h"
40 #include "internal.h"
41 #include "video.h"
42 
43 enum MetadataMode {
44     METADATA_SELECT,
45     METADATA_ADD,
46     METADATA_MODIFY,
47     METADATA_DELETE,
48     METADATA_PRINT,
49     METADATA_NB
50 };
51 
52 enum MetadataFunction {
53     METADATAF_SAME_STR,
54     METADATAF_STARTS_WITH,
55     METADATAF_LESS,
56     METADATAF_EQUAL,
57     METADATAF_GREATER,
58     METADATAF_EXPR,
59     METADATAF_ENDS_WITH,
60     METADATAF_NB
61 };
62 
63 static const char *const var_names[] = {
64     "VALUE1",
65     "VALUE2",
66     "FRAMEVAL",
67     "USERVAL",
68     NULL
69 };
70 
71 enum var_name {
72     VAR_VALUE1,
73     VAR_VALUE2,
74     VAR_FRAMEVAL,
75     VAR_USERVAL,
76     VAR_VARS_NB
77 };
78 
79 typedef struct MetadataContext {
80     const AVClass *class;
81 
82     int mode;
83     char *key;
84     char *value;
85     int function;
86 
87     char *expr_str;
88     AVExpr *expr;
89     double var_values[VAR_VARS_NB];
90 
91     AVIOContext* avio_context;
92     char *file_str;
93 
94     int (*compare)(struct MetadataContext *s,
95                    const char *value1, const char *value2);
96     void (*print)(AVFilterContext *ctx, const char *msg, ...) av_printf_format(2, 3);
97 
98     int direct;    // reduces buffering when printing to user-supplied URL
99 } MetadataContext;
100 
101 #define OFFSET(x) offsetof(MetadataContext, x)
102 #define DEFINE_OPTIONS(filt_name, FLAGS) \
103 static const AVOption filt_name##_options[] = { \
104     { "mode", "set a mode of operation", OFFSET(mode),   AV_OPT_TYPE_INT,    {.i64 = 0 }, 0, METADATA_NB-1, FLAGS, "mode" }, \
105     {   "select", "select frame",        0,              AV_OPT_TYPE_CONST,  {.i64 = METADATA_SELECT }, 0, 0, FLAGS, "mode" }, \
106     {   "add",    "add new metadata",    0,              AV_OPT_TYPE_CONST,  {.i64 = METADATA_ADD },    0, 0, FLAGS, "mode" }, \
107     {   "modify", "modify metadata",     0,              AV_OPT_TYPE_CONST,  {.i64 = METADATA_MODIFY }, 0, 0, FLAGS, "mode" }, \
108     {   "delete", "delete metadata",     0,              AV_OPT_TYPE_CONST,  {.i64 = METADATA_DELETE }, 0, 0, FLAGS, "mode" }, \
109     {   "print",  "print metadata",      0,              AV_OPT_TYPE_CONST,  {.i64 = METADATA_PRINT },  0, 0, FLAGS, "mode" }, \
110     { "key",   "set metadata key",       OFFSET(key),    AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, FLAGS }, \
111     { "value", "set metadata value",     OFFSET(value),  AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, FLAGS }, \
112     { "function", "function for comparing values", OFFSET(function), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, METADATAF_NB-1, FLAGS, "function" }, \
113     {   "same_str",    NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_SAME_STR },    0, 3, FLAGS, "function" }, \
114     {   "starts_with", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_STARTS_WITH }, 0, 0, FLAGS, "function" }, \
115     {   "less",        NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_LESS    },     0, 3, FLAGS, "function" }, \
116     {   "equal",       NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_EQUAL   },     0, 3, FLAGS, "function" }, \
117     {   "greater",     NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_GREATER },     0, 3, FLAGS, "function" }, \
118     {   "expr",        NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_EXPR    },     0, 3, FLAGS, "function" }, \
119     {   "ends_with",   NULL, 0, AV_OPT_TYPE_CONST, {.i64 = METADATAF_ENDS_WITH },   0, 0, FLAGS, "function" }, \
120     { "expr", "set expression for expr function", OFFSET(expr_str), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, FLAGS }, \
121     { "file", "set file where to print metadata information", OFFSET(file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, \
122     { "direct", "reduce buffering when printing to user-set file or pipe", OFFSET(direct), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS }, \
123     { NULL } \
124 }
125 
same_str(MetadataContext * s,const char * value1,const char * value2)126 static int same_str(MetadataContext *s, const char *value1, const char *value2)
127 {
128     return !strcmp(value1, value2);
129 }
130 
starts_with(MetadataContext * s,const char * value1,const char * value2)131 static int starts_with(MetadataContext *s, const char *value1, const char *value2)
132 {
133     return !strncmp(value1, value2, strlen(value2));
134 }
135 
ends_with(MetadataContext * s,const char * value1,const char * value2)136 static int ends_with(MetadataContext *s, const char *value1, const char *value2)
137 {
138     const int len1 = strlen(value1);
139     const int len2 = strlen(value2);
140 
141     return !strncmp(value1 + FFMAX(len1 - len2, 0), value2, len2);
142 }
143 
equal(MetadataContext * s,const char * value1,const char * value2)144 static int equal(MetadataContext *s, const char *value1, const char *value2)
145 {
146     float f1, f2;
147 
148     if (sscanf(value1, "%f", &f1) + sscanf(value2, "%f", &f2) != 2)
149         return 0;
150 
151     return fabsf(f1 - f2) < FLT_EPSILON;
152 }
153 
less(MetadataContext * s,const char * value1,const char * value2)154 static int less(MetadataContext *s, const char *value1, const char *value2)
155 {
156     float f1, f2;
157 
158     if (sscanf(value1, "%f", &f1) + sscanf(value2, "%f", &f2) != 2)
159         return 0;
160 
161     return (f1 - f2) < FLT_EPSILON;
162 }
163 
greater(MetadataContext * s,const char * value1,const char * value2)164 static int greater(MetadataContext *s, const char *value1, const char *value2)
165 {
166     float f1, f2;
167 
168     if (sscanf(value1, "%f", &f1) + sscanf(value2, "%f", &f2) != 2)
169         return 0;
170 
171     return (f2 - f1) < FLT_EPSILON;
172 }
173 
parse_expr(MetadataContext * s,const char * value1,const char * value2)174 static int parse_expr(MetadataContext *s, const char *value1, const char *value2)
175 {
176     double f1, f2;
177 
178     if (sscanf(value1, "%lf", &f1) + sscanf(value2, "%lf", &f2) != 2)
179         return 0;
180 
181     s->var_values[VAR_VALUE1] = s->var_values[VAR_FRAMEVAL] = f1;
182     s->var_values[VAR_VALUE2] = s->var_values[VAR_USERVAL]  = f2;
183 
184     return av_expr_eval(s->expr, s->var_values, NULL);
185 }
186 
print_log(AVFilterContext * ctx,const char * msg,...)187 static void print_log(AVFilterContext *ctx, const char *msg, ...)
188 {
189     va_list argument_list;
190 
191     va_start(argument_list, msg);
192     if (msg)
193         av_vlog(ctx, AV_LOG_INFO, msg, argument_list);
194     va_end(argument_list);
195 }
196 
print_file(AVFilterContext * ctx,const char * msg,...)197 static void print_file(AVFilterContext *ctx, const char *msg, ...)
198 {
199     MetadataContext *s = ctx->priv;
200     va_list argument_list;
201 
202     va_start(argument_list, msg);
203     if (msg) {
204         char buf[128];
205         vsnprintf(buf, sizeof(buf), msg, argument_list);
206         avio_write(s->avio_context, buf, av_strnlen(buf, sizeof(buf)));
207     }
208     va_end(argument_list);
209 }
210 
init(AVFilterContext * ctx)211 static av_cold int init(AVFilterContext *ctx)
212 {
213     MetadataContext *s = ctx->priv;
214     int ret;
215 
216     if (!s->key && s->mode != METADATA_PRINT && s->mode != METADATA_DELETE) {
217         av_log(ctx, AV_LOG_WARNING, "Metadata key must be set\n");
218         return AVERROR(EINVAL);
219     }
220 
221     if ((s->mode == METADATA_MODIFY ||
222         s->mode == METADATA_ADD) && !s->value) {
223         av_log(ctx, AV_LOG_WARNING, "Missing metadata value\n");
224         return AVERROR(EINVAL);
225     }
226 
227     switch (s->function) {
228     case METADATAF_SAME_STR:
229         s->compare = same_str;
230         break;
231     case METADATAF_STARTS_WITH:
232         s->compare = starts_with;
233         break;
234     case METADATAF_ENDS_WITH:
235         s->compare = ends_with;
236         break;
237     case METADATAF_LESS:
238         s->compare = less;
239         break;
240     case METADATAF_EQUAL:
241         s->compare = equal;
242         break;
243     case METADATAF_GREATER:
244         s->compare = greater;
245         break;
246     case METADATAF_EXPR:
247         s->compare = parse_expr;
248         break;
249     default:
250         av_assert0(0);
251     };
252 
253     if (s->function == METADATAF_EXPR) {
254         if (!s->expr_str) {
255             av_log(ctx, AV_LOG_WARNING, "expr option not set\n");
256             return AVERROR(EINVAL);
257         }
258         if ((ret = av_expr_parse(&s->expr, s->expr_str,
259                                  var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
260             av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n", s->expr_str);
261             return ret;
262         }
263     }
264 
265     if (s->mode == METADATA_PRINT && s->file_str) {
266         s->print = print_file;
267     } else {
268         s->print = print_log;
269     }
270 
271     s->avio_context = NULL;
272     if (s->file_str) {
273         if (!strcmp("-", s->file_str)) {
274             ret = avio_open(&s->avio_context, "pipe:1", AVIO_FLAG_WRITE);
275         } else {
276             ret = avio_open(&s->avio_context, s->file_str, AVIO_FLAG_WRITE);
277         }
278 
279         if (ret < 0) {
280             char buf[128];
281             av_strerror(ret, buf, sizeof(buf));
282             av_log(ctx, AV_LOG_ERROR, "Could not open %s: %s\n",
283                    s->file_str, buf);
284             return ret;
285         }
286 
287         if (s->direct)
288             s->avio_context->direct = AVIO_FLAG_DIRECT;
289     }
290 
291     return 0;
292 }
293 
uninit(AVFilterContext * ctx)294 static av_cold void uninit(AVFilterContext *ctx)
295 {
296     MetadataContext *s = ctx->priv;
297 
298     av_expr_free(s->expr);
299     s->expr = NULL;
300     if (s->avio_context) {
301         avio_closep(&s->avio_context);
302     }
303 }
304 
filter_frame(AVFilterLink * inlink,AVFrame * frame)305 static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
306 {
307     AVFilterContext *ctx = inlink->dst;
308     AVFilterLink *outlink = ctx->outputs[0];
309     MetadataContext *s = ctx->priv;
310     AVDictionary **metadata = &frame->metadata;
311     AVDictionaryEntry *e;
312 
313     e = av_dict_get(*metadata, !s->key ? "" : s->key, NULL,
314                     !s->key ? AV_DICT_IGNORE_SUFFIX: 0);
315 
316     switch (s->mode) {
317     case METADATA_SELECT:
318         if (!s->value && e && e->value) {
319             return ff_filter_frame(outlink, frame);
320         } else if (s->value && e && e->value &&
321                    s->compare(s, e->value, s->value)) {
322             return ff_filter_frame(outlink, frame);
323         }
324         break;
325     case METADATA_ADD:
326         if (e && e->value) {
327             ;
328         } else {
329             av_dict_set(metadata, s->key, s->value, 0);
330         }
331         return ff_filter_frame(outlink, frame);
332     case METADATA_MODIFY:
333         if (e && e->value) {
334             av_dict_set(metadata, s->key, s->value, 0);
335         }
336         return ff_filter_frame(outlink, frame);
337     case METADATA_PRINT:
338         if (!s->key && e) {
339             s->print(ctx, "frame:%-4"PRId64" pts:%-7s pts_time:%s\n",
340                      inlink->frame_count_out, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &inlink->time_base));
341             s->print(ctx, "%s=%s\n", e->key, e->value);
342             while ((e = av_dict_get(*metadata, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL) {
343                 s->print(ctx, "%s=%s\n", e->key, e->value);
344             }
345         } else if (e && e->value && (!s->value || (e->value && s->compare(s, e->value, s->value)))) {
346             s->print(ctx, "frame:%-4"PRId64" pts:%-7s pts_time:%s\n",
347                      inlink->frame_count_out, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &inlink->time_base));
348             s->print(ctx, "%s=%s\n", s->key, e->value);
349         }
350         return ff_filter_frame(outlink, frame);
351     case METADATA_DELETE:
352         if (!s->key) {
353             av_dict_free(metadata);
354         } else if (e && e->value && (!s->value || s->compare(s, e->value, s->value))) {
355             av_dict_set(metadata, s->key, NULL, 0);
356         }
357         return ff_filter_frame(outlink, frame);
358     default:
359         av_assert0(0);
360     };
361 
362     av_frame_free(&frame);
363 
364     return 0;
365 }
366 
367 #if CONFIG_AMETADATA_FILTER
368 
369 DEFINE_OPTIONS(ametadata, AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM);
370 AVFILTER_DEFINE_CLASS(ametadata);
371 
372 static const AVFilterPad ainputs[] = {
373     {
374         .name         = "default",
375         .type         = AVMEDIA_TYPE_AUDIO,
376         .filter_frame = filter_frame,
377     },
378 };
379 
380 static const AVFilterPad aoutputs[] = {
381     {
382         .name = "default",
383         .type = AVMEDIA_TYPE_AUDIO,
384     },
385 };
386 
387 const AVFilter ff_af_ametadata = {
388     .name          = "ametadata",
389     .description   = NULL_IF_CONFIG_SMALL("Manipulate audio frame metadata."),
390     .priv_size     = sizeof(MetadataContext),
391     .priv_class    = &ametadata_class,
392     .init          = init,
393     .uninit        = uninit,
394     FILTER_INPUTS(ainputs),
395     FILTER_OUTPUTS(aoutputs),
396     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
397                      AVFILTER_FLAG_METADATA_ONLY,
398 };
399 #endif /* CONFIG_AMETADATA_FILTER */
400 
401 #if CONFIG_METADATA_FILTER
402 
403 DEFINE_OPTIONS(metadata, AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM);
404 AVFILTER_DEFINE_CLASS(metadata);
405 
406 static const AVFilterPad inputs[] = {
407     {
408         .name         = "default",
409         .type         = AVMEDIA_TYPE_VIDEO,
410         .filter_frame = filter_frame,
411     },
412 };
413 
414 static const AVFilterPad outputs[] = {
415     {
416         .name = "default",
417         .type = AVMEDIA_TYPE_VIDEO,
418     },
419 };
420 
421 const AVFilter ff_vf_metadata = {
422     .name        = "metadata",
423     .description = NULL_IF_CONFIG_SMALL("Manipulate video frame metadata."),
424     .priv_size   = sizeof(MetadataContext),
425     .priv_class  = &metadata_class,
426     .init        = init,
427     .uninit      = uninit,
428     FILTER_INPUTS(inputs),
429     FILTER_OUTPUTS(outputs),
430     .flags       = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
431                    AVFILTER_FLAG_METADATA_ONLY,
432 };
433 #endif /* CONFIG_METADATA_FILTER */
434