ffprobe: add compact writer
authorStefano Sabatini <stefasab@gmail.com>
Sun, 2 Oct 2011 09:25:44 +0000 (11:25 +0200)
committerStefano Sabatini <stefasab@gmail.com>
Tue, 15 Nov 2011 23:32:08 +0000 (00:32 +0100)
Changelog
doc/ffprobe.texi
ffprobe.c

index 0b2d13b..f635e88 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -121,6 +121,7 @@ easier to use. The changes are:
 - Discworld II BMV decoding support
 - VBLE Decoder
 - OS X Video Decoder Acceleration (VDA) support
+- compact output in ffprobe
 
 
 version 0.8:
index 9496fc9..172e35d 100644 (file)
@@ -147,6 +147,58 @@ keyN=valN
 Metadata tags are printed as a line in the corresponding FORMAT or
 STREAM section, and are prefixed by the string "TAG:".
 
+@section compact
+Compact format.
+
+Each section is printed on a single line.
+If no option is specifid, the output has the form:
+@example
+section|key1=val1| ... |keyN=valN
+@end example
+
+Metadata tags are printed in the corresponding "format" or "stream"
+section. A metadata tag key, if printed, is prefixed by the string
+"tag:".
+
+This writer accepts options as a list of @var{key}=@var{value} pairs,
+separated by ":".
+
+The description of the accepted options follows.
+
+@table @option
+
+@item item_sep, s
+Specify the character to use for separating fields in the output line.
+It must be a single printable character, it is "|" by default.
+
+@item nokey, nk
+If set to 1 specify not to print the key of each field. Its default
+value is 0.
+
+@item escape, e
+Set the escape mode to use, default to "c".
+
+It can assume one of the following values:
+@table @option
+@item c
+Perform C-like escaping. Strings containing a newline ('\n') or
+carriage return ('\r'), the escaping character ('\') or the item
+separator character @var{SEP} are escaped using C-like fashioned
+escaping, so that a newline is converted to the sequence "\n", a
+carriage return to "\r", '\' to "\\" and the separator @var{SEP} is
+converted to "\@var{SEP}".
+
+@item csv
+Perform CSV-like escaping, as described in RFC4180.  Strings
+containing a newline ('\n'), a carriage return ('\r'), a double quote
+('"'), or @var{SEP} are enclosed in double-quotes.
+
+@item none
+Perform no escaping.
+@end table
+
+@end table
+
 @section json
 JSON based format.
 
index a8c2ff0..c15cf7b 100644 (file)
--- a/ffprobe.c
+++ b/ffprobe.c
@@ -462,6 +462,232 @@ static Writer default_writer = {
     .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS,
 };
 
+/* Compact output */
+
+/**
+ * Escape \n, \r, \\ and sep characters contained in s, and print the
+ * resulting string.
+ */
+static const char *c_escape_str(char **dst, size_t *dst_size,
+                                const char *src, const char sep, void *log_ctx)
+{
+    const char *p;
+    char *q;
+    size_t size = 1;
+
+    /* precompute size */
+    for (p = src; *p; p++, size++) {
+        ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-2);
+        if (*p == '\n' || *p == '\r' || *p == '\\')
+            size++;
+    }
+
+    ESCAPE_REALLOC_BUF(dst_size, dst, src, size);
+
+    q = *dst;
+    for (p = src; *p; p++) {
+        switch (*src) {
+        case '\n': *q++ = '\\'; *q++ = 'n';  break;
+        case '\r': *q++ = '\\'; *q++ = 'r';  break;
+        case '\\': *q++ = '\\'; *q++ = '\\'; break;
+        default:
+            if (*p == sep)
+                *q++ = '\\';
+            *q++ = *p;
+        }
+    }
+    *q = 0;
+    return *dst;
+}
+
+/**
+ * Quote fields containing special characters, check RFC4180.
+ */
+static const char *csv_escape_str(char **dst, size_t *dst_size,
+                                  const char *src, const char sep, void *log_ctx)
+{
+    const char *p;
+    char *q;
+    size_t size = 1;
+    int quote = 0;
+
+    /* precompute size */
+    for (p = src; *p; p++, size++) {
+        ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-4);
+        if (*p == '"' || *p == sep || *p == '\n' || *p == '\r')
+            if (!quote) {
+                quote = 1;
+                size += 2;
+            }
+        if (*p == '"')
+            size++;
+    }
+
+    ESCAPE_REALLOC_BUF(dst_size, dst, src, size);
+
+    q = *dst;
+    p = src;
+    if (quote)
+        *q++ = '\"';
+    while (*p) {
+        if (*p == '"')
+            *q++ = '\"';
+        *q++ = *p++;
+    }
+    if (quote)
+        *q++ = '\"';
+    *q = 0;
+
+    return *dst;
+}
+
+static const char *none_escape_str(char **dst, size_t *dst_size,
+                                   const char *src, const char sep, void *log_ctx)
+{
+    return src;
+}
+
+typedef struct CompactContext {
+    const AVClass *class;
+    char *item_sep_str;
+    char item_sep;
+    int nokey;
+    char  *buf;
+    size_t buf_size;
+    char *escape_mode_str;
+    const char * (*escape_str)(char **dst, size_t *dst_size,
+                               const char *src, const char sep, void *log_ctx);
+} CompactContext;
+
+#define OFFSET(x) offsetof(CompactContext, x)
+
+static const AVOption compact_options[]= {
+    {"item_sep", "set item separator",    OFFSET(item_sep_str),    AV_OPT_TYPE_STRING, {.str="|"},  CHAR_MIN, CHAR_MAX },
+    {"s",        "set item separator",    OFFSET(item_sep_str),    AV_OPT_TYPE_STRING, {.str="|"},  CHAR_MIN, CHAR_MAX },
+    {"nokey",    "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_INT,    {.dbl=0},    0,        1        },
+    {"nk",       "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_INT,    {.dbl=0},    0,        1        },
+    {"escape",   "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"},  CHAR_MIN, CHAR_MAX },
+    {"e",        "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"},  CHAR_MIN, CHAR_MAX },
+    {NULL},
+};
+
+static const char *compact_get_name(void *ctx)
+{
+    return "compact";
+}
+
+static const AVClass compact_class = {
+    "CompactContext",
+    compact_get_name,
+    compact_options
+};
+
+static av_cold int compact_init(WriterContext *wctx, const char *args, void *opaque)
+{
+    CompactContext *compact = wctx->priv;
+    int err;
+
+    compact->class = &compact_class;
+    av_opt_set_defaults(compact);
+
+    if (args &&
+        (err = (av_set_options_string(compact, args, "=", ":"))) < 0) {
+        av_log(wctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args);
+        return err;
+    }
+    if (strlen(compact->item_sep_str) != 1) {
+        av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
+               compact->item_sep_str);
+        return AVERROR(EINVAL);
+    }
+    compact->item_sep = compact->item_sep_str[0];
+
+    compact->buf_size = ESCAPE_INIT_BUF_SIZE;
+    if (!(compact->buf = av_malloc(compact->buf_size)))
+        return AVERROR(ENOMEM);
+
+    if      (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str;
+    else if (!strcmp(compact->escape_mode_str, "c"   )) compact->escape_str = c_escape_str;
+    else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str;
+    else {
+        av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str);
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+static av_cold void compact_uninit(WriterContext *wctx)
+{
+    CompactContext *compact = wctx->priv;
+
+    av_freep(&compact->item_sep_str);
+    av_freep(&compact->buf);
+    av_freep(&compact->escape_mode_str);
+}
+
+static void compact_print_section_header(WriterContext *wctx, const char *section)
+{
+    CompactContext *compact = wctx->priv;
+
+    printf("%s%c", section, compact->item_sep);
+}
+
+static void compact_print_section_footer(WriterContext *wctx, const char *section)
+{
+    printf("\n");
+}
+
+static void compact_print_str(WriterContext *wctx, const char *key, const char *value)
+{
+    CompactContext *compact = wctx->priv;
+
+    if (wctx->nb_item) printf("%c", compact->item_sep);
+    if (!compact->nokey)
+        printf("%s=", key);
+    printf("%s", compact->escape_str(&compact->buf, &compact->buf_size,
+                                     value, compact->item_sep, wctx));
+}
+
+static void compact_print_int(WriterContext *wctx, const char *key, int value)
+{
+    CompactContext *compact = wctx->priv;
+
+    if (wctx->nb_item) printf("%c", compact->item_sep);
+    if (!compact->nokey)
+        printf("%s=", key);
+    printf("%d", value);
+}
+
+static void compact_show_tags(WriterContext *wctx, AVDictionary *dict)
+{
+    CompactContext *compact = wctx->priv;
+    AVDictionaryEntry *tag = NULL;
+
+    while ((tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX))) {
+        if (wctx->nb_item) printf("%c", compact->item_sep);
+        if (!compact->nokey)
+            printf("tag:%s=", compact->escape_str(&compact->buf, &compact->buf_size,
+                                                  tag->key, compact->item_sep, wctx));
+        printf("%s", compact->escape_str(&compact->buf, &compact->buf_size,
+                                         tag->value, compact->item_sep, wctx));
+    }
+}
+
+static Writer compact_writer = {
+    .name          = "compact",
+    .priv_size     = sizeof(CompactContext),
+
+    .init                  = compact_init,
+    .uninit                = compact_uninit,
+    .print_section_header  = compact_print_section_header,
+    .print_section_footer  = compact_print_section_footer,
+    .print_integer         = compact_print_int,
+    .print_string          = compact_print_str,
+    .show_tags             = compact_show_tags,
+    .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS
+};
+
 /* JSON output */
 
 typedef struct {
@@ -630,6 +856,7 @@ static void writer_register_all(void)
     initialized = 1;
 
     writer_register(&default_writer);
+    writer_register(&compact_writer);
     writer_register(&json_writer);
 }
 
@@ -976,7 +1203,7 @@ static const OptionDef options[] = {
       "use sexagesimal format HOURS:MM:SS.MICROSECONDS for time units" },
     { "pretty", 0, {(void*)&opt_pretty},
       "prettify the format of displayed values, make it more human readable" },
-    { "print_format", OPT_STRING | HAS_ARG, {(void*)&print_format}, "set the output printing format (available formats are: default, json)", "format" },
+    { "print_format", OPT_STRING | HAS_ARG, {(void*)&print_format}, "set the output printing format (available formats are: default, compact, json)", "format" },
     { "show_format",  OPT_BOOL, {(void*)&do_show_format} , "show format/container info" },
     { "show_packets", OPT_BOOL, {(void*)&do_show_packets}, "show packets info" },
     { "show_streams", OPT_BOOL, {(void*)&do_show_streams}, "show streams info" },