[FFmpeg-devel,1/1] New helper expr for the select filter

Submitted by bmathew@gmail.com on Feb. 5, 2018, 9:48 p.m.

Details

Message ID 20180205214845.25243-2-bmathew@gmail.com
State New
Headers show

Commit Message

bmathew@gmail.com Feb. 5, 2018, 9:48 p.m.
Added a new expr/function get(n, filename) in libavutil/eval.c that is
meant to be used in conjunction with the select filter and greatly
enhances the generality of the select filter.

The select filter as it exists now can be used to selectively enable frames
during transcoding.  e.g.: select='not(mod(n\,100))' will enable one frame every
100.

It is often required to do much more complex frame by frame selection under the
control of an external program. For example, a face detector might indicate
frames on which certain individuals have been detected and we wish to get a
video with only frames where those individuals are present. Another example is
video of lab experiments synchronized with some external sensor and we wish to
extract the frames where the sensor detected some event.

The get operator may be used as follows:
-vf 'select=get(n\,"frame_select.en")'

frame_select.en is a file that contains 1 byte per frame of the input video.  It
can be generated by the external program (e.g.: a face detector, lab software
that processes sensor data). The get operator looks up the n-th byte in the file
and passes it on to select so that frames can be kept or deleted. Very general
editing operations can be realized by having an external program generate the
enable file.

Implementation:

New struct AVGetExprBuffer has been added that is used to load the contents of
the frame select file only when a get() expression is present. eval_expr() was
modified to return the appropriate frame enable value from the buffer.

No changes to the libavutil API. The changes are completely contained within
eval.c
---
 libavutil/eval.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 116 insertions(+), 2 deletions(-)

Patch hide | download patch | download mbox

diff --git a/libavutil/eval.c b/libavutil/eval.c
index 5da9a6d83b..58e3742245 100644
--- a/libavutil/eval.c
+++ b/libavutil/eval.c
@@ -142,6 +142,40 @@  double av_strtod(const char *numstr, char **tail)
     return d;
 }
 
+static int parse_string_literal(Parser *p, char **tail, char **literal_str)
+{
+  char *parse_str = p->s;
+  char *next = parse_str;
+  char *literal = NULL;
+  int nbyte;
+
+    if(parse_str[0] == '\'' || parse_str[0] == '"') {
+
+      next = strchr(parse_str + 1, parse_str[0]);
+            
+      if(next == NULL) {
+        av_log(p, AV_LOG_ERROR, "Unterminated string in '%s'\n", parse_str);
+        return AVERROR(EINVAL);
+      } else {
+        nbyte = next - parse_str; /* Includes space for \0 */
+        literal = av_mallocz(nbyte);
+        if (!literal)
+          return AVERROR(ENOMEM);
+
+        strncpy(literal, parse_str+1, nbyte-1);
+        literal[nbyte-1] = '\0';
+        next++;                 /* step past the ending quote */
+      }
+    }
+
+    /* if requested, fill in tail with the position after the last parsed
+       character */
+    if (tail)
+        *tail = next;
+    *literal_str = literal;
+    return 0;
+}
+
 #define IS_IDENTIFIER_CHAR(c) ((c) - '0' <= 9U || (c) - 'a' <= 25U || (c) - 'A' <= 25U || (c) == '_')
 
 static int strmatch(const char *s, const char *prefix)
@@ -154,17 +188,27 @@  static int strmatch(const char *s, const char *prefix)
     return !IS_IDENTIFIER_CHAR(s[i]);
 }
 
+
+struct AVGetExprBuffer {
+  int ndata;
+  char *data;
+};
+
 struct AVExpr {
     enum {
-        e_value, e_const, e_func0, e_func1, e_func2,
+        e_value, e_const, e_string, e_buffer, e_func0, e_func1, e_func2,
         e_squish, e_gauss, e_ld, e_isnan, e_isinf,
         e_mod, e_max, e_min, e_eq, e_gt, e_gte, e_lte, e_lt,
         e_pow, e_mul, e_div, e_add,
         e_last, e_st, e_while, e_taylor, e_root, e_floor, e_ceil, e_trunc, e_round,
         e_sqrt, e_not, e_random, e_hypot, e_gcd,
         e_if, e_ifnot, e_print, e_bitand, e_bitor, e_between, e_clip, e_atan2, e_lerp,
+        e_get,
     } type;
-    double value; // is sign in other types
+    union {
+        double value; // is sign in other types
+        void *pointer;
+    };
     union {
         int const_index;
         double (*func0)(double);
@@ -300,6 +344,12 @@  static double eval_expr(Parser *p, AVExpr *e)
             p->var[0] = var0;
             return -low_v<high_v ? low : high;
         }
+        case e_get: {
+          struct AVGetExprBuffer * buffer = (struct AVGetExprBuffer * )e->param[1]->pointer;
+          int index = (int)eval_expr(p, e->param[0]);
+          index = (index < 0)?0:((index >= buffer->ndata)?buffer->ndata-1:index);
+          return e->value * buffer->data[index];
+        }
         default: {
             double d = eval_expr(p, e->param[0]);
             double d2 = eval_expr(p, e->param[1]);
@@ -345,11 +395,23 @@  static int parse_primary(AVExpr **e, Parser *p)
 {
     AVExpr *d = av_mallocz(sizeof(AVExpr));
     char *next = p->s, *s0 = p->s;
+    char * literal_str;
     int ret, i;
 
     if (!d)
         return AVERROR(ENOMEM);
 
+    if((ret = parse_string_literal(p, &next, &literal_str)) != 0)
+        return ret;
+
+    if(literal_str != NULL) {
+        d->type = e_string;
+        d->pointer = literal_str;
+        p->s = next;
+        *e = d;
+        return 0;
+    }
+
     /* number */
     d->value = av_strtod(p->s, &next);
     if (next != p->s) {
@@ -470,6 +532,7 @@  static int parse_primary(AVExpr **e, Parser *p)
     else if (strmatch(next, "clip"  )) d->type = e_clip;
     else if (strmatch(next, "atan2" )) d->type = e_atan2;
     else if (strmatch(next, "lerp"  )) d->type = e_lerp;
+    else if (strmatch(next, "get"   )) d->type = e_get;
     else {
         for (i=0; p->func1_names && p->func1_names[i]; i++) {
             if (strmatch(next, p->func1_names[i])) {
@@ -637,12 +700,55 @@  static int parse_expr(AVExpr **e, Parser *p)
     return 0;
 }
 
+
+static int init_get_expr(AVExpr *e)
+{
+  FILE * fp;
+  char * fname = (char *)e->pointer;
+  struct AVGetExprBuffer * buffer;
+  int nbyte;
+
+  if((fp = fopen(fname, "rb")) == NULL)
+    return 0;
+  fseek(fp, 0, SEEK_END);
+  nbyte = ftell(fp);
+  fseek(fp, 0, SEEK_SET);
+
+  buffer = av_malloc(nbyte + sizeof(struct AVGetExprBuffer));
+  if(buffer == NULL)
+    {
+      fclose(fp);
+      return 0;
+    }
+
+  buffer->ndata = nbyte;
+  buffer->data = (char *)(buffer + 1);  /* Add sizeof the struct */
+
+  if(fread(buffer->data, nbyte, 1, fp) != 1)
+    {
+      fclose(fp);
+      av_free(buffer);
+      return 0;
+    }
+
+  e->pointer = buffer;
+  e->type = e_buffer;
+  av_free(fname);
+  fclose(fp);
+
+  return 1;
+}
+
 static int verify_expr(AVExpr *e)
 {
     if (!e) return 0;
     switch (e->type) {
         case e_value:
         case e_const: return 1;
+        /* string and buffer types are currently only exposed to e_get.
+           So if we got here, something is wrong. */
+        case e_string:
+        case e_buffer: return 0; 
         case e_func0:
         case e_func1:
         case e_squish:
@@ -672,6 +778,14 @@  static int verify_expr(AVExpr *e)
             return verify_expr(e->param[0]) &&
                    verify_expr(e->param[1]) &&
                    verify_expr(e->param[2]);
+        case e_get:
+          if(!e->param[1] || e->param[1]->type != e_string)
+            return 0;
+          /* Since there is no separate init for the expression, and nothing
+             other than get currently needs initialization, initializing 
+             from verify_expr() was the least amount of new code. */
+          return verify_expr(e->param[0]) && init_get_expr(e->param[1]);
+
         default: return verify_expr(e->param[0]) && verify_expr(e->param[1]) && !e->param[2];
     }
 }