]> git.sesse.net Git - ffmpeg/blob - libavfilter/f_sendcmd.c
avfilter/f_sendcmd: implement expr flag
[ffmpeg] / libavfilter / f_sendcmd.c
1 /*
2  * Copyright (c) 2012 Stefano Sabatini
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  * send commands filter
24  */
25
26 #include "libavutil/avstring.h"
27 #include "libavutil/bprint.h"
28 #include "libavutil/eval.h"
29 #include "libavutil/file.h"
30 #include "libavutil/opt.h"
31 #include "libavutil/parseutils.h"
32 #include "avfilter.h"
33 #include "internal.h"
34 #include "audio.h"
35 #include "video.h"
36
37 #define COMMAND_FLAG_ENTER 1
38 #define COMMAND_FLAG_LEAVE 2
39 #define COMMAND_FLAG_EXPR  4
40
41 static const char *const var_names[] = {
42     "N",     /* frame number */
43     "T",     /* frame time in seconds */
44     "POS",   /* original position in the file of the frame */
45     "PTS",   /* frame pts */
46     NULL
47 };
48
49 enum var_name {
50     VAR_N,
51     VAR_T,
52     VAR_POS,
53     VAR_PTS,
54     VAR_VARS_NB
55 };
56
57 static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
58 {
59     static const char * const flag_strings[] = { "enter", "leave", "expr" };
60     int i, is_first = 1;
61
62     av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
63     for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) {
64         if (flags & 1<<i) {
65             if (!is_first)
66                 av_bprint_chars(pbuf, '+', 1);
67             av_bprintf(pbuf, "%s", flag_strings[i]);
68             is_first = 0;
69         }
70     }
71
72     return pbuf->str;
73 }
74
75 typedef struct Command {
76     int flags;
77     char *target, *command, *arg;
78     int index;
79 } Command;
80
81 typedef struct Interval {
82     int64_t start_ts;          ///< start timestamp expressed as microseconds units
83     int64_t end_ts;            ///< end   timestamp expressed as microseconds units
84     int index;                 ///< unique index for these interval commands
85     Command *commands;
86     int   nb_commands;
87     int enabled;               ///< current time detected inside this interval
88 } Interval;
89
90 typedef struct SendCmdContext {
91     const AVClass *class;
92     Interval *intervals;
93     int   nb_intervals;
94
95     char *commands_filename;
96     char *commands_str;
97 } SendCmdContext;
98
99 #define OFFSET(x) offsetof(SendCmdContext, x)
100 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
101 static const AVOption options[] = {
102     { "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
103     { "c",        "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
104     { "filename", "set commands file",  OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
105     { "f",        "set commands file",  OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
106     { NULL }
107 };
108
109 #define SPACES " \f\t\n\r"
110
111 static void skip_comments(const char **buf)
112 {
113     while (**buf) {
114         /* skip leading spaces */
115         *buf += strspn(*buf, SPACES);
116         if (**buf != '#')
117             break;
118
119         (*buf)++;
120
121         /* skip comment until the end of line */
122         *buf += strcspn(*buf, "\n");
123         if (**buf)
124             (*buf)++;
125     }
126 }
127
128 #define COMMAND_DELIMS " \f\t\n\r,;"
129
130 static int parse_command(Command *cmd, int cmd_count, int interval_count,
131                          const char **buf, void *log_ctx)
132 {
133     int ret;
134
135     memset(cmd, 0, sizeof(Command));
136     cmd->index = cmd_count;
137
138     /* format: [FLAGS] target command arg */
139     *buf += strspn(*buf, SPACES);
140
141     /* parse flags */
142     if (**buf == '[') {
143         (*buf)++; /* skip "[" */
144
145         while (**buf) {
146             int len = strcspn(*buf, "|+]");
147
148             if      (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER;
149             else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE;
150             else if (!strncmp(*buf, "expr",  strlen("expr")))  cmd->flags |= COMMAND_FLAG_EXPR;
151             else {
152                 char flag_buf[64];
153                 av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
154                 av_log(log_ctx, AV_LOG_ERROR,
155                        "Unknown flag '%s' in interval #%d, command #%d\n",
156                        flag_buf, interval_count, cmd_count);
157                 return AVERROR(EINVAL);
158             }
159             *buf += len;
160             if (**buf == ']')
161                 break;
162             if (!strspn(*buf, "+|")) {
163                 av_log(log_ctx, AV_LOG_ERROR,
164                        "Invalid flags char '%c' in interval #%d, command #%d\n",
165                        **buf, interval_count, cmd_count);
166                 return AVERROR(EINVAL);
167             }
168             if (**buf)
169                 (*buf)++;
170         }
171
172         if (**buf != ']') {
173             av_log(log_ctx, AV_LOG_ERROR,
174                    "Missing flag terminator or extraneous data found at the end of flags "
175                    "in interval #%d, command #%d\n", interval_count, cmd_count);
176             return AVERROR(EINVAL);
177         }
178         (*buf)++; /* skip "]" */
179     } else {
180         cmd->flags = COMMAND_FLAG_ENTER;
181     }
182
183     *buf += strspn(*buf, SPACES);
184     cmd->target = av_get_token(buf, COMMAND_DELIMS);
185     if (!cmd->target || !cmd->target[0]) {
186         av_log(log_ctx, AV_LOG_ERROR,
187                "No target specified in interval #%d, command #%d\n",
188                interval_count, cmd_count);
189         ret = AVERROR(EINVAL);
190         goto fail;
191     }
192
193     *buf += strspn(*buf, SPACES);
194     cmd->command = av_get_token(buf, COMMAND_DELIMS);
195     if (!cmd->command || !cmd->command[0]) {
196         av_log(log_ctx, AV_LOG_ERROR,
197                "No command specified in interval #%d, command #%d\n",
198                interval_count, cmd_count);
199         ret = AVERROR(EINVAL);
200         goto fail;
201     }
202
203     *buf += strspn(*buf, SPACES);
204     cmd->arg = av_get_token(buf, COMMAND_DELIMS);
205
206     return 1;
207
208 fail:
209     av_freep(&cmd->target);
210     av_freep(&cmd->command);
211     av_freep(&cmd->arg);
212     return ret;
213 }
214
215 static int parse_commands(Command **cmds, int *nb_cmds, int interval_count,
216                           const char **buf, void *log_ctx)
217 {
218     int cmd_count = 0;
219     int ret, n = 0;
220     AVBPrint pbuf;
221
222     *cmds = NULL;
223     *nb_cmds = 0;
224
225     while (**buf) {
226         Command cmd;
227
228         if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0)
229             return ret;
230         cmd_count++;
231
232         /* (re)allocate commands array if required */
233         if (*nb_cmds == n) {
234             n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
235             *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command));
236             if (!*cmds) {
237                 av_log(log_ctx, AV_LOG_ERROR,
238                        "Could not (re)allocate command array\n");
239                 return AVERROR(ENOMEM);
240             }
241         }
242
243         (*cmds)[(*nb_cmds)++] = cmd;
244
245         *buf += strspn(*buf, SPACES);
246         if (**buf && **buf != ';' && **buf != ',') {
247             av_log(log_ctx, AV_LOG_ERROR,
248                    "Missing separator or extraneous data found at the end of "
249                    "interval #%d, in command #%d\n",
250                    interval_count, cmd_count);
251             av_log(log_ctx, AV_LOG_ERROR,
252                    "Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n",
253                    make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg);
254             return AVERROR(EINVAL);
255         }
256         if (**buf == ';')
257             break;
258         if (**buf == ',')
259             (*buf)++;
260     }
261
262     return 0;
263 }
264
265 #define DELIMS " \f\t\n\r,;"
266
267 static int parse_interval(Interval *interval, int interval_count,
268                           const char **buf, void *log_ctx)
269 {
270     char *intervalstr;
271     int ret;
272
273     *buf += strspn(*buf, SPACES);
274     if (!**buf)
275         return 0;
276
277     /* reset data */
278     memset(interval, 0, sizeof(Interval));
279     interval->index = interval_count;
280
281     /* format: INTERVAL COMMANDS */
282
283     /* parse interval */
284     intervalstr = av_get_token(buf, DELIMS);
285     if (intervalstr && intervalstr[0]) {
286         char *start, *end;
287
288         start = av_strtok(intervalstr, "-", &end);
289         if (!start) {
290             ret = AVERROR(EINVAL);
291             av_log(log_ctx, AV_LOG_ERROR,
292                    "Invalid interval specification '%s' in interval #%d\n",
293                    intervalstr, interval_count);
294             goto end;
295         }
296         if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) {
297             av_log(log_ctx, AV_LOG_ERROR,
298                    "Invalid start time specification '%s' in interval #%d\n",
299                    start, interval_count);
300             goto end;
301         }
302
303         if (end) {
304             if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) {
305                 av_log(log_ctx, AV_LOG_ERROR,
306                        "Invalid end time specification '%s' in interval #%d\n",
307                        end, interval_count);
308                 goto end;
309             }
310         } else {
311             interval->end_ts = INT64_MAX;
312         }
313         if (interval->end_ts < interval->start_ts) {
314             av_log(log_ctx, AV_LOG_ERROR,
315                    "Invalid end time '%s' in interval #%d: "
316                    "cannot be lesser than start time '%s'\n",
317                    end, interval_count, start);
318             ret = AVERROR(EINVAL);
319             goto end;
320         }
321     } else {
322         av_log(log_ctx, AV_LOG_ERROR,
323                "No interval specified for interval #%d\n", interval_count);
324         ret = AVERROR(EINVAL);
325         goto end;
326     }
327
328     /* parse commands */
329     ret = parse_commands(&interval->commands, &interval->nb_commands,
330                          interval_count, buf, log_ctx);
331
332 end:
333     av_free(intervalstr);
334     return ret;
335 }
336
337 static int parse_intervals(Interval **intervals, int *nb_intervals,
338                            const char *buf, void *log_ctx)
339 {
340     int interval_count = 0;
341     int ret, n = 0;
342
343     *intervals = NULL;
344     *nb_intervals = 0;
345
346     if (!buf)
347         return 0;
348
349     while (1) {
350         Interval interval;
351
352         skip_comments(&buf);
353         if (!(*buf))
354             break;
355
356         if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0)
357             return ret;
358
359         buf += strspn(buf, SPACES);
360         if (*buf) {
361             if (*buf != ';') {
362                 av_log(log_ctx, AV_LOG_ERROR,
363                        "Missing terminator or extraneous data found at the end of interval #%d\n",
364                        interval_count);
365                 return AVERROR(EINVAL);
366             }
367             buf++; /* skip ';' */
368         }
369         interval_count++;
370
371         /* (re)allocate commands array if required */
372         if (*nb_intervals == n) {
373             n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
374             *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval));
375             if (!*intervals) {
376                 av_log(log_ctx, AV_LOG_ERROR,
377                        "Could not (re)allocate intervals array\n");
378                 return AVERROR(ENOMEM);
379             }
380         }
381
382         (*intervals)[(*nb_intervals)++] = interval;
383     }
384
385     return 0;
386 }
387
388 static int cmp_intervals(const void *a, const void *b)
389 {
390     const Interval *i1 = a;
391     const Interval *i2 = b;
392     return 2 * FFDIFFSIGN(i1->start_ts, i2->start_ts) + FFDIFFSIGN(i1->index, i2->index);
393 }
394
395 static av_cold int init(AVFilterContext *ctx)
396 {
397     SendCmdContext *s = ctx->priv;
398     int ret, i, j;
399
400     if ((!!s->commands_filename + !!s->commands_str) != 1) {
401         av_log(ctx, AV_LOG_ERROR,
402                "One and only one of the filename or commands options must be specified\n");
403         return AVERROR(EINVAL);
404     }
405
406     if (s->commands_filename) {
407         uint8_t *file_buf, *buf;
408         size_t file_bufsize;
409         ret = av_file_map(s->commands_filename,
410                           &file_buf, &file_bufsize, 0, ctx);
411         if (ret < 0)
412             return ret;
413
414         /* create a 0-terminated string based on the read file */
415         buf = av_malloc(file_bufsize + 1);
416         if (!buf) {
417             av_file_unmap(file_buf, file_bufsize);
418             return AVERROR(ENOMEM);
419         }
420         memcpy(buf, file_buf, file_bufsize);
421         buf[file_bufsize] = 0;
422         av_file_unmap(file_buf, file_bufsize);
423         s->commands_str = buf;
424     }
425
426     if ((ret = parse_intervals(&s->intervals, &s->nb_intervals,
427                                s->commands_str, ctx)) < 0)
428         return ret;
429
430     if (s->nb_intervals == 0) {
431         av_log(ctx, AV_LOG_ERROR, "No commands were specified\n");
432         return AVERROR(EINVAL);
433     }
434
435     qsort(s->intervals, s->nb_intervals, sizeof(Interval), cmp_intervals);
436
437     av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
438     for (i = 0; i < s->nb_intervals; i++) {
439         AVBPrint pbuf;
440         Interval *interval = &s->intervals[i];
441         av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n",
442                (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index);
443         for (j = 0; j < interval->nb_commands; j++) {
444             Command *cmd = &interval->commands[j];
445             av_log(ctx, AV_LOG_VERBOSE,
446                    "    [%s] target:%s command:%s arg:%s index:%d\n",
447                    make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index);
448         }
449     }
450
451     return 0;
452 }
453
454 static av_cold void uninit(AVFilterContext *ctx)
455 {
456     SendCmdContext *s = ctx->priv;
457     int i, j;
458
459     for (i = 0; i < s->nb_intervals; i++) {
460         Interval *interval = &s->intervals[i];
461         for (j = 0; j < interval->nb_commands; j++) {
462             Command *cmd = &interval->commands[j];
463             av_freep(&cmd->target);
464             av_freep(&cmd->command);
465             av_freep(&cmd->arg);
466         }
467         av_freep(&interval->commands);
468     }
469     av_freep(&s->intervals);
470 }
471
472 #define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
473 #define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts)*av_q2d(tb))
474
475 static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
476 {
477     AVFilterContext *ctx = inlink->dst;
478     SendCmdContext *s = ctx->priv;
479     int64_t ts;
480     int i, j, ret;
481
482     if (ref->pts == AV_NOPTS_VALUE)
483         goto end;
484
485     ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q);
486
487 #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts))
488
489     for (i = 0; i < s->nb_intervals; i++) {
490         Interval *interval = &s->intervals[i];
491         int flags = 0;
492
493         if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
494             flags += COMMAND_FLAG_ENTER;
495             interval->enabled = 1;
496         }
497         if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
498             flags += COMMAND_FLAG_LEAVE;
499             interval->enabled = 0;
500         }
501         if (interval->enabled)
502             flags += COMMAND_FLAG_EXPR;
503
504         if (flags) {
505             AVBPrint pbuf;
506             av_log(ctx, AV_LOG_VERBOSE,
507                    "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n",
508                    make_command_flags_str(&pbuf, flags), interval->index,
509                    (double)interval->start_ts/1000000, (double)interval->end_ts/1000000,
510                    (double)ts/1000000);
511
512             for (j = 0; flags && j < interval->nb_commands; j++) {
513                 Command *cmd = &interval->commands[j];
514                 char *cmd_arg = cmd->arg;
515                 char buf[1024];
516
517                 if (cmd->flags & flags) {
518                     if (cmd->flags & COMMAND_FLAG_EXPR) {
519                         double var_values[VAR_VARS_NB], res;
520
521                         var_values[VAR_N]   = inlink->frame_count_in;
522                         var_values[VAR_POS] = ref->pkt_pos == -1 ? NAN : ref->pkt_pos;
523                         var_values[VAR_PTS] = TS2D(ref->pts);
524                         var_values[VAR_T]   = TS2T(ref->pts, inlink->time_base);
525
526                         if ((ret = av_expr_parse_and_eval(&res, cmd->arg, var_names, var_values,
527                                                           NULL, NULL, NULL, NULL, NULL, 0, NULL)) < 0) {
528                             av_log(ctx, AV_LOG_ERROR, "Invalid expression '%s' for command argument.\n", cmd->arg);
529                             av_frame_free(&ref);
530                             return AVERROR(EINVAL);
531                         }
532
533                         cmd_arg = av_asprintf("%g", res);
534                         if (!cmd_arg) {
535                             av_frame_free(&ref);
536                             return AVERROR(ENOMEM);
537                         }
538                     }
539                     av_log(ctx, AV_LOG_VERBOSE,
540                            "Processing command #%d target:%s command:%s arg:%s\n",
541                            cmd->index, cmd->target, cmd->command, cmd_arg);
542                     ret = avfilter_graph_send_command(inlink->graph,
543                                                       cmd->target, cmd->command, cmd_arg,
544                                                       buf, sizeof(buf),
545                                                       AVFILTER_CMD_FLAG_ONE);
546                     av_log(ctx, AV_LOG_VERBOSE,
547                            "Command reply for command #%d: ret:%s res:%s\n",
548                            cmd->index, av_err2str(ret), buf);
549                     if (cmd->flags & COMMAND_FLAG_EXPR)
550                         av_freep(&cmd_arg);
551                 }
552             }
553         }
554     }
555
556 end:
557     switch (inlink->type) {
558     case AVMEDIA_TYPE_VIDEO:
559     case AVMEDIA_TYPE_AUDIO:
560         return ff_filter_frame(inlink->dst->outputs[0], ref);
561     }
562
563     return AVERROR(ENOSYS);
564 }
565
566 #if CONFIG_SENDCMD_FILTER
567
568 #define sendcmd_options options
569 AVFILTER_DEFINE_CLASS(sendcmd);
570
571 static const AVFilterPad sendcmd_inputs[] = {
572     {
573         .name         = "default",
574         .type         = AVMEDIA_TYPE_VIDEO,
575         .filter_frame = filter_frame,
576     },
577     { NULL }
578 };
579
580 static const AVFilterPad sendcmd_outputs[] = {
581     {
582         .name = "default",
583         .type = AVMEDIA_TYPE_VIDEO,
584     },
585     { NULL }
586 };
587
588 AVFilter ff_vf_sendcmd = {
589     .name        = "sendcmd",
590     .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
591     .init        = init,
592     .uninit      = uninit,
593     .priv_size   = sizeof(SendCmdContext),
594     .inputs      = sendcmd_inputs,
595     .outputs     = sendcmd_outputs,
596     .priv_class  = &sendcmd_class,
597 };
598
599 #endif
600
601 #if CONFIG_ASENDCMD_FILTER
602
603 #define asendcmd_options options
604 AVFILTER_DEFINE_CLASS(asendcmd);
605
606 static const AVFilterPad asendcmd_inputs[] = {
607     {
608         .name         = "default",
609         .type         = AVMEDIA_TYPE_AUDIO,
610         .filter_frame = filter_frame,
611     },
612     { NULL }
613 };
614
615 static const AVFilterPad asendcmd_outputs[] = {
616     {
617         .name = "default",
618         .type = AVMEDIA_TYPE_AUDIO,
619     },
620     { NULL }
621 };
622
623 AVFilter ff_af_asendcmd = {
624     .name        = "asendcmd",
625     .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
626     .init        = init,
627     .uninit      = uninit,
628     .priv_size   = sizeof(SendCmdContext),
629     .inputs      = asendcmd_inputs,
630     .outputs     = asendcmd_outputs,
631     .priv_class  = &asendcmd_class,
632 };
633
634 #endif