FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/f_metadata.c
Date: 2022-12-09 07:38:14
Exec Total Coverage
Lines: 58 147 39.5%
Functions: 5 12 41.7%
Branches: 27 110 24.5%

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 "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
126 static int same_str(MetadataContext *s, const char *value1, const char *value2)
127 {
128 return !strcmp(value1, value2);
129 }
130
131 static int starts_with(MetadataContext *s, const char *value1, const char *value2)
132 {
133 return !strncmp(value1, value2, strlen(value2));
134 }
135
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
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
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
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
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
187 126 static void print_log(AVFilterContext *ctx, const char *msg, ...)
188 {
189 va_list argument_list;
190
191 126 va_start(argument_list, msg);
192
1/2
✓ Branch 0 taken 126 times.
✗ Branch 1 not taken.
126 if (msg)
193 126 av_vlog(ctx, AV_LOG_INFO, msg, argument_list);
194 126 va_end(argument_list);
195 126 }
196
197 227 static void print_file(AVFilterContext *ctx, const char *msg, ...)
198 {
199 227 MetadataContext *s = ctx->priv;
200 va_list argument_list;
201
202 227 va_start(argument_list, msg);
203
1/2
✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
227 if (msg) {
204 char buf[128];
205 227 vsnprintf(buf, sizeof(buf), msg, argument_list);
206 227 avio_write(s->avio_context, buf, av_strnlen(buf, sizeof(buf)));
207 }
208 227 va_end(argument_list);
209 227 }
210
211 24 static av_cold int init(AVFilterContext *ctx)
212 {
213 24 MetadataContext *s = ctx->priv;
214 int ret;
215
216
2/6
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
24 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
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if ((s->mode == METADATA_MODIFY ||
222
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
24 s->mode == METADATA_ADD) && !s->value) {
223 av_log(ctx, AV_LOG_WARNING, "Missing metadata value\n");
224 return AVERROR(EINVAL);
225 }
226
227
1/8
✓ Branch 0 taken 24 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.
24 switch (s->function) {
228 24 case METADATAF_SAME_STR:
229 24 s->compare = same_str;
230 24 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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 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
3/4
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 2 times.
24 if (s->mode == METADATA_PRINT && s->file_str) {
266 22 s->print = print_file;
267 } else {
268 2 s->print = print_log;
269 }
270
271 24 s->avio_context = NULL;
272
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2 times.
24 if (s->file_str) {
273
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (!strcmp("-", s->file_str)) {
274 22 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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (s->direct)
288 s->avio_context->direct = AVIO_FLAG_DIRECT;
289 }
290
291 24 return 0;
292 }
293
294 24 static av_cold void uninit(AVFilterContext *ctx)
295 {
296 24 MetadataContext *s = ctx->priv;
297
298 24 av_expr_free(s->expr);
299 24 s->expr = NULL;
300
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2 times.
24 if (s->avio_context) {
301 22 avio_closep(&s->avio_context);
302 }
303 24 }
304
305 57 static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
306 {
307 57 AVFilterContext *ctx = inlink->dst;
308 57 AVFilterLink *outlink = ctx->outputs[0];
309 57 MetadataContext *s = ctx->priv;
310 57 AVDictionary **metadata = &frame->metadata;
311 const AVDictionaryEntry *e;
312
313
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 57 times.
57 e = av_dict_get(*metadata, !s->key ? "" : s->key, NULL,
314
1/2
✓ Branch 0 taken 57 times.
✗ Branch 1 not taken.
57 !s->key ? AV_DICT_IGNORE_SUFFIX: 0);
315
316
1/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 57 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
57 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 57 case METADATA_PRINT:
338
3/4
✓ Branch 0 taken 57 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 53 times.
✓ Branch 3 taken 4 times.
110 if (!s->key && e) {
339 53 s->print(ctx, "frame:%-4"PRId64" pts:%-7s pts_time:%s\n",
340 53 inlink->frame_count_out, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &inlink->time_base));
341 53 s->print(ctx, "%s=%s\n", e->key, e->value);
342
2/2
✓ Branch 1 taken 247 times.
✓ Branch 2 taken 53 times.
300 while ((e = av_dict_iterate(*metadata, e)) != NULL) {
343 247 s->print(ctx, "%s=%s\n", e->key, e->value);
344 }
345
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)))) {
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 57 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