From patchwork Wed Feb 24 19:53:54 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Werner Robitza X-Patchwork-Id: 25973 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id F2B874494FC for ; Wed, 24 Feb 2021 22:01:45 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id BE87B6880C1; Wed, 24 Feb 2021 22:01:45 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ed1-f42.google.com (mail-ed1-f42.google.com [209.85.208.42]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 82306688059 for ; Wed, 24 Feb 2021 22:01:39 +0200 (EET) Received: by mail-ed1-f42.google.com with SMTP id s8so4124978edd.5 for ; Wed, 24 Feb 2021 12:01:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=EC/0osjKzeWxQ141c02valNqNQ6nSm1UXZuIPw/8RPM=; b=Wl2OBn4lVBQGpf7vsEiZAyhvw2755af7l8DxJ49Hub6v/WBWgoT0vGqzvdsgc4/7W+ 8YsG+Un7I8m879ZQiYlDRBh3ZLvrAgpZv6I50pTWNMqJHyXQ1Im+rShoa47VdhiVsw9O Rrv3c4O87OunqLvtvbylliu9rCMEAS46QlV/ZD76aQX3LYDRls3i5F1ALlhGB4EhU74z /w0Leeg3q6XJlR5neJiiTd3jE6PG1tRYmb3rd2iAzFdw2TOGkiqgLAWWbRKVxbOgRbpo d30XHUdupSYCGonEcvNIXHwqW7k2VOFdHBhCbWoVmqPop4sFbfsSBP0ELHKMNtnhycRM 6puQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=EC/0osjKzeWxQ141c02valNqNQ6nSm1UXZuIPw/8RPM=; b=czaR419YD1Mt6LO4s0u2jXINdGQU1NV796Kklc1z/unFneqlY+x1H0TNKtsvs2fVLr pvQTwgrctcTxfPWi+Dd0F+9GN4Zd1ihLSX2zvhIHxKLxNRmcl7ozBtQXU1YCFQqkjwsi T6JYtW9rTaNBs+C/UbJE4fZztY/Blkelo+q45hgezR6ZXJAAkvR6CoYY5Nbz26kjRmQ4 0FkIgzq+hkwn/d8FYBXo3UgY7ZK3SXIJl9Vdoy3LYW+hw81H4i8zkcd+5muTVk05K2X2 A7ONmiEEwrRA5W2vdCuJiQ7FN3Go6IdSldE19PcouG0+80DDJahEV8YEG5TKQA59T2JR j2gQ== X-Gm-Message-State: AOAM533XTmh+rXuEagFWJiwbDNcUAZyDLYjZIqaXopBf5gqsvZNyZCw/ h/VxQMiaAPqpaasPEWCfU2+dJFrLH4k= X-Google-Smtp-Source: ABdhPJyU76EwscpxtxoUoreuWgwJev3A1O4qJmXA5q5KGcYFeYH7K3PMnfZWafi15l2v+SAQeCwJLA== X-Received: by 2002:a50:d7c7:: with SMTP id m7mr34299800edj.260.1614196437602; Wed, 24 Feb 2021 11:53:57 -0800 (PST) Received: from localhost.localdomain (80-110-81-45.cgn.dynamic.surfer.at. [80.110.81.45]) by smtp.gmail.com with ESMTPSA id lj13sm1810439ejb.123.2021.02.24.11.53.56 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 24 Feb 2021 11:53:56 -0800 (PST) From: Werner Robitza To: ffmpeg-devel@ffmpeg.org Date: Wed, 24 Feb 2021 20:53:54 +0100 Message-Id: <20210224195354.60821-1-werner.robitza@gmail.com> X-Mailer: git-send-email 2.30.0 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH] filters/metadata: add CSV output support X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Werner Robitza Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" 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 --- 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: