This adds a new option to the metadata filter that allows outputting CSV data. The separator can be set via another option. Special characters are handled via escaping.
Signed-off-by: Werner Robitza <werner.robi...@gmail.com> --- doc/filters.texi | 14 ++++ libavfilter/f_metadata.c | 155 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 158 insertions(+), 11 deletions(-) diff --git a/doc/filters.texi b/doc/filters.texi index 426cb158da..0b56d73565 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -25134,6 +25134,20 @@ with AV_LOG_INFO loglevel. @item direct Reduces buffering in print mode when output is written to a URL set using @var{file}. +@item format +Set the output format for the print mode. + +@table @samp +@item default +Default output format + +@item csv +Comma-separated output +@end table + +@item csv_sep +Set the CSV separator (only valid for CSV format). Default is @code{,}. Must be +one character and cannot be a period (@code{.}). @end table @subsection Examples diff --git a/libavfilter/f_metadata.c b/libavfilter/f_metadata.c index 598257b15b..45f5eeddcd 100644 --- a/libavfilter/f_metadata.c +++ b/libavfilter/f_metadata.c @@ -58,6 +58,11 @@ enum MetadataFunction { METADATAF_NB }; +enum MetadataFormat { + METADATA_FORMAT_DEFAULT, + METADATA_FORMAT_CSV +}; + static const char *const var_names[] = { "VALUE1", "VALUE2", @@ -85,6 +90,9 @@ typedef struct MetadataContext { AVIOContext* avio_context; char *file_str; + int format; + char *csv_sep; + int (*compare)(struct MetadataContext *s, const char *value1, const char *value2); void (*print)(AVFilterContext *ctx, const char *msg, ...) av_printf_format(2, 3); @@ -114,6 +122,10 @@ static const AVOption filt_name##_options[] = { \ { "expr", "set expression for expr function", OFFSET(expr_str), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, FLAGS }, \ { "file", "set file where to print metadata information", OFFSET(file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, \ { "direct", "reduce buffering when printing to user-set file or pipe", OFFSET(direct), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS }, \ + { "format", "set output format", OFFSET(format), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, METADATAF_NB-1, FLAGS, "format" }, \ + { "default", "default format", 0, AV_OPT_TYPE_CONST, {.i64 = METADATA_FORMAT_DEFAULT }, 0, 0, FLAGS, "format" }, \ + { "csv", "comma-separated", 0, AV_OPT_TYPE_CONST, {.i64 = METADATA_FORMAT_CSV }, 0, 0, FLAGS, "format" }, \ + { "csv_sep", "set CSV separator (only valid for CSV format)", OFFSET(csv_sep), AV_OPT_TYPE_STRING, {.str=","}, 0, 0, FLAGS }, \ { NULL } \ } @@ -202,6 +214,58 @@ static void print_file(AVFilterContext *ctx, const char *msg, ...) va_end(argument_list); } +static void print_csv_escaped(AVFilterContext *ctx, const char *src) +{ + MetadataContext *s = ctx->priv; + + char meta_chars[] = {s->csv_sep[0], '"', '\n', '\r', '\0'}; + int needs_quoting = !!src[strcspn(src, meta_chars)]; + + // allocate space for two extra quotes and possibly every char escaped + char buf[strlen(src) * 2 + 2]; + + int pos = 0; + + if (needs_quoting) + buf[pos++] = '"'; + + for (int i = 0; i < strlen(src); i++) { + if (src[i] == '"') + buf[pos++] = '\"'; + buf[pos++] = src[i]; + } + + if (needs_quoting) + buf[pos++] = '"'; + + buf[pos] = '\0'; + + s->print(ctx, "%s", buf); +} + +static void csv_escape(const char *src, char **dst, const char csv_sep) +{ + char meta_chars[] = {csv_sep, '"', '\n', '\r', '\0'}; + int needs_quoting = !!src[strcspn(src, meta_chars)]; + + int pos = 0; + + if (needs_quoting) + *dst[pos++] = '"'; + + for (int i = 0; i < strlen(src); i++) + { + if (src[i] == '"') + *dst[pos++] = '\"'; + *dst[pos++] = src[i]; + } + + if (needs_quoting) + *dst[pos++] = '"'; + + *dst[pos] = '\0'; +} + static av_cold int init(AVFilterContext *ctx) { MetadataContext *s = ctx->priv; @@ -282,6 +346,33 @@ static av_cold int init(AVFilterContext *ctx) s->avio_context->direct = AVIO_FLAG_DIRECT; } + if (s->format == METADATA_FORMAT_CSV) { + if (strlen(s->csv_sep) == 0) { + av_log(ctx, AV_LOG_ERROR, + "No CSV separator supplied\n"); + return AVERROR(EINVAL); + } + if (strlen(s->csv_sep) > 1) { + av_log(ctx, AV_LOG_ERROR, + "CSV separator must be one character only\n"); + return AVERROR(EINVAL); + } + if (s->csv_sep[0] == '.') { + av_log(ctx, AV_LOG_ERROR, + "CSV separator cannot be a single period ('.')\n"); + return AVERROR(EINVAL); + } + s->print( + ctx, + "%s%s%s%s%s%s%s%s%s\n", + "frame", s->csv_sep, + "pts", s->csv_sep, + "pts_time", s->csv_sep, + "key", s->csv_sep, + "value" + ); + } + return 0; } @@ -332,17 +423,59 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame) } return ff_filter_frame(outlink, frame); case METADATA_PRINT: - if (!s->key && e) { - s->print(ctx, "frame:%-4"PRId64" pts:%-7s pts_time:%s\n", - inlink->frame_count_out, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &inlink->time_base)); - s->print(ctx, "%s=%s\n", e->key, e->value); - while ((e = av_dict_get(*metadata, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL) { - s->print(ctx, "%s=%s\n", e->key, e->value); - } - } else if (e && e->value && (!s->value || (e->value && s->compare(s, e->value, s->value)))) { - s->print(ctx, "frame:%-4"PRId64" pts:%-7s pts_time:%s\n", - inlink->frame_count_out, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &inlink->time_base)); - s->print(ctx, "%s=%s\n", s->key, e->value); + switch(s->format) { + case METADATA_FORMAT_DEFAULT: + if (!s->key && e) { + s->print(ctx, "frame:%-4"PRId64" pts:%-7s pts_time:%s\n", + inlink->frame_count_out, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &inlink->time_base)); + s->print(ctx, "%s=%s\n", e->key, e->value); + while ((e = av_dict_get(*metadata, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL) { + s->print(ctx, "%s=%s\n", e->key, e->value); + } + } else if (e && e->value && (!s->value || (e->value && s->compare(s, e->value, s->value)))) { + s->print(ctx, "frame:%-4"PRId64" pts:%-7s pts_time:%s\n", + inlink->frame_count_out, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &inlink->time_base)); + s->print(ctx, "%s=%s\n", s->key, e->value); + } + break; + case METADATA_FORMAT_CSV: + if (!s->key && e) { + s->print( + ctx, "%"PRId64"%s%s%s%s%s", + inlink->frame_count_out, s->csv_sep, + av_ts2str(frame->pts), s->csv_sep, + av_ts2timestr(frame->pts, &inlink->time_base), s->csv_sep + ); + print_csv_escaped(ctx, e->key); + s->print(ctx, "%s", s->csv_sep); + print_csv_escaped(ctx, e->value); + while ((e = av_dict_get(*metadata, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL) { + s->print( + ctx, "%"PRId64"%s%s%s%s%s", + inlink->frame_count_out, s->csv_sep, + av_ts2str(frame->pts), s->csv_sep, + av_ts2timestr(frame->pts, &inlink->time_base), s->csv_sep + ); + print_csv_escaped(ctx, e->key); + s->print(ctx, "%s", s->csv_sep); + print_csv_escaped(ctx, e->value); + s->print(ctx, "\n"); + } + } else if (e && e->value && (!s->value || (e->value && s->compare(s, e->value, s->value)))) { + s->print( + ctx, "%"PRId64"%s%s%s%s%s", + inlink->frame_count_out, s->csv_sep, + av_ts2str(frame->pts), s->csv_sep, + av_ts2timestr(frame->pts, &inlink->time_base), s->csv_sep + ); + print_csv_escaped(ctx, e->key); + s->print(ctx, "%s", s->csv_sep); + print_csv_escaped(ctx, e->value); + s->print(ctx, "\n"); + } + break; + default: + av_assert0(0); } return ff_filter_frame(outlink, frame); case METADATA_DELETE: -- 2.30.0 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".