[FFmpeg-devel] [PATCH] libavfilter/f_select: response file support
Jonathan Gilbert
logic at deltaq.org
Tue Apr 16 20:14:13 EEST 2019
Hello :-)
I had a project recently where I wanted to externally specify a list
of specific frame numbers to drop. I understand this can be done with
select expressions like "not(eq(n,45)+eq(n,47)+eq(n,75))", but in my
case I wanted to drop nearly 16,000 frames, which would have required
a command-line over 250 KB in size. I chose a different approach: I
have added functionality to f_select.c so that you can specify the
list of frames you want to include/exclude in an external response
file. I have successfully used this code for my project, and thought I
might submit it up to for consideration. :-)
I've never submitted a patch in this format before, I hope I'm doing
this correctly.
Thanks very much,
Jonathan Gilbert
---
libavfilter/f_select.c | 316 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 310 insertions(+), 6 deletions(-)
diff --git a/libavfilter/f_select.c b/libavfilter/f_select.c
index 1132375758..0852fffb44 100644
--- a/libavfilter/f_select.c
+++ b/libavfilter/f_select.c
@@ -22,6 +22,8 @@
* @file
* filter for selecting which frame passes in the filterchain
*/
+#include <stdio.h>
+#include <ctype.h>
#include "libavutil/avstring.h"
#include "libavutil/eval.h"
@@ -139,10 +141,33 @@ enum var_name {
VAR_VARS_NB
};
+enum filemode {
+ SELECT_FILEMODE_INCLUDE,
+ SELECT_FILEMODE_EXCLUDE
+};
+
+enum filetype {
+ SELECT_FILETYPE_FRAMENO,
+ SELECT_FILETYPE_PTS
+};
+
+enum warnunused {
+ SELECT_WARNUNUSED_NO,
+ SELECT_WARNUNUSED_YES,
+ SELECT_WARNUNUSED_ALL
+};
+
typedef struct SelectContext {
const AVClass *class;
char *expr_str;
AVExpr *expr;
+ char *file_str;
+ int *file_frame_list;
+ int file_frame_list_length;
+ enum filemode filemode;
+ enum filetype filetype;
+ char *file_frame_list_used;
+ enum warnunused file_warn_unused;
double var_values[VAR_VARS_NB];
int do_scene_detect; ///< 1 if the expression requires
scene detection variables, 0 otherwise
ff_scene_sad_fn sad; ///< Sum of the absolute
difference function (scene detect only)
@@ -158,6 +183,17 @@ typedef struct SelectContext {
static const AVOption filt_name##_options[] = { \
{ "expr", "set an expression to use for selecting frames",
OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags=FLAGS },
\
{ "e", "set an expression to use for selecting frames",
OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags=FLAGS },
\
+ { "file", "set the name of a file from which frame numbers
are read", OFFSET(file_str), AV_OPT_TYPE_STRING, { .str = "" },
.flags=FLAGS }, \
+ { "filemode", "set the interpretation of the file (include or
exclude)", OFFSET(filemode), AV_OPT_TYPE_INT, { .i64 =
SELECT_FILEMODE_INCLUDE }, INT_MIN, INT_MAX, FLAGS, "filemode" }, \
+ { "include", "include all frames listed in the file", 0,
AV_OPT_TYPE_CONST, { .i64 = SELECT_FILEMODE_INCLUDE }, INT_MIN,
INT_MAX, FLAGS, "filemode" }, \
+ { "exclude", "exclude all frames listed in the file", 0,
AV_OPT_TYPE_CONST, { .i64 = SELECT_FILEMODE_EXCLUDE }, INT_MIN,
INT_MAX, FLAGS, "filemode" }, \
+ { "filetype", "set the interpretation of the file (frameno or
pts)", OFFSET(filetype), AV_OPT_TYPE_INT, { .i64 =
SELECT_FILETYPE_FRAMENO }, INT_MIN, INT_MAX, FLAGS, "filetype" }, \
+ { "frameno", "match frames by frame number", 0,
AV_OPT_TYPE_CONST, { .i64 = SELECT_FILETYPE_FRAMENO }, INT_MIN,
INT_MAX, FLAGS, "filetype" }, \
+ { "pts", "match frames by pts", 0, AV_OPT_TYPE_CONST, { .i64
= SELECT_FILETYPE_PTS }, INT_MIN, INT_MAX, FLAGS, "filetype" }, \
+ { "warnunused", "if enabled, warns if any of the frame numbers
from the input file are never matched", OFFSET(file_warn_unused),
AV_OPT_TYPE_INT, { .i64 = SELECT_WARNUNUSED_NO }, INT_MIN, INT_MAX,
FLAGS, "warnunused" }, \
+ { "no", "do not warn about unused frame numbers from the
input file", 0, AV_OPT_TYPE_CONST, { .i64 = SELECT_WARNUNUSED_NO },
INT_MIN, INT_MAX, FLAGS, "warnunused" }, \
+ { "yes", "warn about unused frame numbers from the input
file, but don't show a long list", 0, AV_OPT_TYPE_CONST, { .i64 =
SELECT_WARNUNUSED_YES }, INT_MIN, INT_MAX, FLAGS, "warnunused" }, \
+ { "all", "warn about all unused frame numbers from the input
file, even if there are many", 0, AV_OPT_TYPE_CONST, { .i64 =
SELECT_WARNUNUSED_ALL }, INT_MIN, INT_MAX, FLAGS, "warnunused" }, \
{ "outputs", "set the number of outputs", OFFSET(nb_outputs),
AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, .flags=FLAGS }, \
{ "n", "set the number of outputs", OFFSET(nb_outputs),
AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, .flags=FLAGS }, \
{ NULL } \
@@ -165,17 +201,220 @@ static const AVOption filt_name##_options[] = {
\
static int request_frame(AVFilterLink *outlink);
+static int read_line(FILE *file, char **buffer, int *buffer_length)
+{
+ int ch;
+ int offset = 0;
+
+ while (1) {
+ ch = fgetc(file);
+
+ if (ch == EOF) {
+ int actual_eof = feof(file);
+
+ if (actual_eof) {
+ if (offset == 0)
+ return 0;
+ else
+ break;
+ }
+ }
+
+ if (ch == '\n')
+ break;
+
+ if (ch == '\r') {
+ ch = fgetc(file);
+
+ if ((ch != EOF) && (ch != '\n'))
+ ungetc(ch, file);
+
+ break;
+ }
+
+ (*buffer)[offset++] = ch;
+
+ if (offset >= *buffer_length) {
+ char *new_buffer;
+
+ *buffer_length += 2 + *buffer_length / 2;
+ new_buffer = (char *)realloc(*buffer, *buffer_length);
+
+ if (new_buffer == NULL)
+ return AVERROR(ENOMEM);
+
+ *buffer = new_buffer;
+ }
+ }
+
+ (*buffer)[offset] = '\0';
+ return 1;
+}
+
+static void strip_string_whitespace(char *str)
+{
+ char *from = str;
+ char *to = str;
+
+ while (isspace(*from))
+ from++;
+
+ if (!*from)
+ *to = '\0';
+ else if (from == to) {
+ while (*str && !isspace(*str))
+ str++;
+
+ *str = '\0';
+ }
+ else {
+ while (*from && !isspace(*from))
+ *to++ = *from++;
+
+ *to = '\0';
+ }
+}
+
+static int read_frame_list_from_file(AVFilterContext *ctx, char
*filename, int **list_ptr, int *list_length_ptr)
+{
+ FILE *file;
+ int buffer_length;
+ char *buffer;
+ int list_length;
+ int list_capacity;
+ int *list;
+
+ file = fopen(filename, "r");
+
+ if (!file) {
+ int ret = AVERROR(errno);
+ av_log(ctx, AV_LOG_ERROR, "Cannot open file '%s' for reading:
%s\n", filename, av_err2str(ret));
+ return ret;
+ }
+
+ buffer_length = 40;
+ buffer = malloc(buffer_length);
+
+ list_capacity = 20;
+ list = (int *)malloc(list_capacity * sizeof(int));
+
+ if ((buffer == NULL) || (list == NULL)) {
+ int ret = AVERROR(ENOMEM);
+ av_log(ctx, AV_LOG_ERROR, "Error reading from file '%s':
%s\n", filename, av_err2str(ret));
+ return ret;
+ }
+
+ list_length = 0;
+
+ while (1) {
+ char *parse_ptr;
+ int parsed;
+
+ int ret = read_line(file, &buffer, &buffer_length);
+
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR, "Error reading from file '%s':
%s\n", filename, av_err2str(ret));
+ free(buffer);
+ free(list);
+ return ret;
+ }
+
+ if (ret == 0)
+ break;
+
+ strip_string_whitespace(buffer);
+
+ if (*buffer == '\0')
+ continue;
+
+ parsed = strtol(buffer, &parse_ptr, 10);
+
+ if ((*parse_ptr != '\0') || (parsed < 0)) {
+ av_log(ctx, AV_LOG_ERROR, "Couldn't parse frame number:
'%s'\n", buffer);
+ free(buffer);
+ free(list);
+ return AVERROR(EINVAL);
+ }
+
+ list[list_length] = parsed;
+ list_length++;
+
+ if (list_length == list_capacity) {
+ int *new_list;
+
+ list_capacity += list_capacity / 2;
+ new_list = (int *)realloc(list, list_capacity * sizeof(int));
+
+ if (new_list == NULL) {
+ int ret = AVERROR(ENOMEM);
+ av_log(ctx, AV_LOG_ERROR, "Error reading from file
'%s': %s\n", filename, av_err2str(ret));
+ free(buffer);
+ free(list);
+ return ret;
+ }
+
+ list = new_list;
+ }
+ }
+
+ *list_ptr = list;
+ *list_length_ptr = list_length;
+
+ free(buffer);
+
+ return 0;
+}
+
+static int compare_integers(const void *a_ptr, const void *b_ptr)
+{
+ int a = *(int *)a_ptr;
+ int b = *(int *)b_ptr;
+
+ return a - b;
+}
+
static av_cold int init(AVFilterContext *ctx)
{
SelectContext *select = ctx->priv;
int i, ret;
- if ((ret = av_expr_parse(&select->expr, select->expr_str,
- var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
- av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n",
- select->expr_str);
- return ret;
+ select->file_frame_list = NULL;
+ select->file_frame_list_used = NULL;
+
+ if (select->file_str && strlen(select->file_str)) {
+ if (select->expr_str && strlen(select->expr_str) &&
strcmp(select->expr_str, "1")) {
+ av_log(ctx, AV_LOG_ERROR, "Cannot specify both a file and
an expression for the select filter\n");
+ return AVERROR(EINVAL);
+ }
+
+ ret = read_frame_list_from_file(ctx, select->file_str,
&select->file_frame_list, &select->file_frame_list_length);
+
+ if (ret < 0)
+ return ret;
+
+ av_log(ctx, AV_LOG_DEBUG, "Read %d frames from frame list
file\n", select->file_frame_list_length);
+
+ qsort(select->file_frame_list,
select->file_frame_list_length, sizeof(select->file_frame_list[0]),
compare_integers);
+
+ for (i = 0; i < select->file_frame_list_length; i++)
+ av_log(ctx, AV_LOG_DEBUG, "[%d] = %d\n", i,
select->file_frame_list[i]);
+
+ if (select->file_warn_unused != SELECT_WARNUNUSED_NO) {
+ av_log(ctx, AV_LOG_DEBUG, "Setting up frame used flags\n");
+
+ select->file_frame_list_used = (char
*)malloc(select->file_frame_list_length * sizeof(char));
+ memset(select->file_frame_list_used, 0,
select->file_frame_list_length * sizeof(char));
+ }
}
+ else {
+ if ((ret = av_expr_parse(&select->expr, select->expr_str,
+ var_names, NULL, NULL, NULL, NULL,
0, ctx)) < 0) {
+ av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n",
+ select->expr_str);
+ return ret;
+ }
+ }
+
select->do_scene_detect = !!strstr(select->expr_str, "scene");
for (i = 0; i < select->nb_outputs; i++) {
@@ -298,7 +537,7 @@ static double get_concatdec_select(AVFrame *frame,
int64_t pts)
#define D2TS(d) (isnan(d) ? AV_NOPTS_VALUE : (int64_t)(d))
#define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
-static void select_frame(AVFilterContext *ctx, AVFrame *frame)
+static void select_frame_expr(AVFilterContext *ctx, AVFrame *frame)
{
SelectContext *select = ctx->priv;
AVFilterLink *inlink = ctx->inputs[0];
@@ -382,6 +621,47 @@ static void select_frame(AVFilterContext *ctx,
AVFrame *frame)
select->var_values[VAR_PREV_T] = select->var_values[VAR_T];
}
+static void select_frame_list(AVFilterContext *ctx, AVFrame *frame)
+{
+ SelectContext *select = ctx->priv;
+ AVFilterLink *inlink = ctx->inputs[0];
+ int search_key = -1;
+
+ av_log(inlink->dst, AV_LOG_DEBUG, "checking a frame");
+
+ if (select->filetype == SELECT_FILETYPE_FRAMENO)
+ search_key = inlink->frame_count_out;
+ else if (select->filetype == SELECT_FILETYPE_PTS)
+ search_key = TS2D(frame->pts);
+
+ int *search_result = (int *)bsearch(&search_key,
select->file_frame_list, select->file_frame_list_length,
sizeof(select->file_frame_list[0]), compare_integers);
+
+ if (select->file_frame_list_used != NULL) {
+ int index = search_result - select->file_frame_list;
+
+ if ((index >= 0) && (index < select->file_frame_list_length))
+ select->file_frame_list_used[index] = 1;
+ }
+
+ int include_frame = (search_result != NULL) ^ (select->filemode
== SELECT_FILEMODE_EXCLUDE);
+
+ select->select = include_frame ? 1 : 0;
+ select->select_out = 0;
+ // TODO: consider supporting multiple outputs with response files?
+
+ av_log(inlink->dst, AV_LOG_DEBUG, " -> search_key:%d
select_out:%d\n", search_key, select->select_out);
+}
+
+static void select_frame(AVFilterContext *ctx, AVFrame *frame)
+{
+ SelectContext *select = ctx->priv;
+
+ if (select->file_frame_list == NULL)
+ select_frame_expr(ctx, frame);
+ else
+ select_frame_list(ctx, frame);
+}
+
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
{
AVFilterContext *ctx = inlink->dst;
@@ -407,6 +687,8 @@ static av_cold void uninit(AVFilterContext *ctx)
SelectContext *select = ctx->priv;
int i;
+ av_log(ctx, AV_LOG_DEBUG, "Uninitializing\n");
+
av_expr_free(select->expr);
select->expr = NULL;
@@ -416,6 +698,28 @@ static av_cold void uninit(AVFilterContext *ctx)
if (select->do_scene_detect) {
av_frame_free(&select->prev_picref);
}
+
+ if ((select->file_warn_unused != SELECT_WARNUNUSED_NO) &&
(select->file_frame_list_used != NULL)) {
+ int unused_count = 0;
+ int i;
+
+ for (i = 0; i < select->file_frame_list_length; i++)
+ if (!select->file_frame_list_used[i])
+ unused_count++;
+
+ av_log(ctx, AV_LOG_ERROR, "Select filter: %d frames indicated
in response file were not matched\n", unused_count);
+
+ if ((unused_count <= 15) || (select->file_warn_unused ==
SELECT_WARNUNUSED_ALL)) {
+ for (i = 0; i < select->file_frame_list_length; i++)
+ if (!select->file_frame_list_used[i])
+ av_log(ctx, AV_LOG_ERROR, "=> %d\n",
select->file_frame_list[i]);
+ }
+
+ free(select->file_frame_list_used);
+ }
+
+ if (select->file_frame_list != NULL)
+ free(select->file_frame_list);
}
#if CONFIG_ASELECT_FILTER
--
2.17.1.windows.2
More information about the ffmpeg-devel
mailing list