[FFmpeg-devel] [PATCH] filters/metadata: add CSV output support

Paul B Mahol onemda at gmail.com
Wed Feb 24 22:11:44 EET 2021


Why duplicating code that would give same output as ffprobe?

On Wed, Feb 24, 2021 at 9:01 PM Werner Robitza <werner.robitza at gmail.com>
wrote:

> 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.robitza at 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}.
>
> + at item format
> +Set the output format for the print mode.
> +
> + at table @samp
> + at item default
> +Default output format
> +
> + at item csv
> +Comma-separated output
> + at end table
> +
> + at 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 at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".


More information about the ffmpeg-devel mailing list