ffprobe: add INI writer
authorStefano Sabatini <stefasab@gmail.com>
Sat, 26 May 2012 23:24:19 +0000 (01:24 +0200)
committerStefano Sabatini <stefasab@gmail.com>
Tue, 29 May 2012 11:06:33 +0000 (13:06 +0200)
Liberally based on the work of Luca Barbato <lu_zero@gentoo.org>, done
for libav/avprobe.

Changelog
doc/ffprobe.texi
ffprobe.c

index fc9f482..ece8d27 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -2,6 +2,8 @@ Entries are sorted chronologically from oldest to youngest within each release,
 releases are sorted from youngest to oldest.
 
 version next:
+- INI output in ffprobe
+
 
 version 0.11:
 Fixes:CVE-2012-2772, CVE-2012-2774, CVE-2012-2775, CVE-2012-2776, CVE-2012-2777,
index cde1d34..1f65668 100644 (file)
@@ -269,6 +269,45 @@ CSV format.
 This writer is equivalent to
 @code{compact=item_sep=,:nokey=1:escape=csv}.
 
+@section ini
+INI format output.
+
+Print output in an INI based format.
+
+The following conventions are adopted:
+
+@itemize
+@item
+all key and values are UTF-8
+@item
+'.' is the subgroup separator
+@item
+newline, '\t', '\f', '\b' and the following characters are escaped
+@item
+'\' is the escape character
+@item
+'#' is the comment indicator
+@item
+'=' is the key/value separator
+@item
+':' is not used but usually parsed as key/value separator
+@end itemize
+
+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 hierarchical, h
+Specify if the section name specification should be hierarchical. If
+set to 1, and if there is more than one section in the current
+chapter, the section name will be prefixed by the name of the
+chapter. A value of 0 will disable this behavior.
+
+Default value is 1.
+@end table
+
 @section json
 JSON based format.
 
index daea512..2727dd7 100644 (file)
--- a/ffprobe.c
+++ b/ffprobe.c
@@ -713,6 +713,174 @@ static const Writer csv_writer = {
     .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS,
 };
 
+/* INI format output */
+
+typedef struct {
+    const AVClass *class;
+    AVBPrint chapter_name, section_name;
+    int print_packets_and_frames;
+    int nb_frame;
+    int nb_packet;
+    int hierarchical;
+} INIContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(INIContext, x)
+
+static const AVOption ini_options[] = {
+    {"hierachical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.dbl=1}, 0, 1 },
+    {"h",           "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.dbl=1}, 0, 1 },
+    {NULL},
+};
+
+static const char *ini_get_name(void *ctx)
+{
+    return "ini";
+}
+
+static const AVClass ini_class = {
+    "INIContext",
+    ini_get_name,
+    ini_options
+};
+
+static av_cold int ini_init(WriterContext *wctx, const char *args, void *opaque)
+{
+    INIContext *ini = wctx->priv;
+    int err;
+
+    av_bprint_init(&ini->chapter_name, 1, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprint_init(&ini->section_name, 1, AV_BPRINT_SIZE_UNLIMITED);
+    ini->nb_frame = ini->nb_packet = 0;
+
+    ini->class = &ini_class;
+    av_opt_set_defaults(ini);
+
+    if (args && (err = av_set_options_string(ini, args, "=", ":")) < 0) {
+        av_log(wctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args);
+        return err;
+    }
+
+    return 0;
+}
+
+static av_cold void ini_uninit(WriterContext *wctx)
+{
+    INIContext *ini = wctx->priv;
+    av_bprint_finalize(&ini->chapter_name, NULL);
+    av_bprint_finalize(&ini->section_name, NULL);
+}
+
+static void ini_print_header(WriterContext *wctx)
+{
+    printf("# ffprobe output\n\n");
+}
+
+static char *ini_escape_str(AVBPrint *dst, const char *src)
+{
+    int i = 0;
+    char c = 0;
+
+    while (c = src[i++]) {
+        switch (c) {
+        case '\b': av_bprintf(dst, "%s", "\\b"); break;
+        case '\f': av_bprintf(dst, "%s", "\\f"); break;
+        case '\n': av_bprintf(dst, "%s", "\\n"); break;
+        case '\r': av_bprintf(dst, "%s", "\\r"); break;
+        case '\t': av_bprintf(dst, "%s", "\\t"); break;
+        case '\\':
+        case '#' :
+        case '=' :
+        case ':' : av_bprint_chars(dst, '\\', 1);
+        default:
+            if ((unsigned char)c < 32)
+                av_bprintf(dst, "\\x00%02x", c & 0xff);
+            else
+                av_bprint_chars(dst, c, 1);
+            break;
+        }
+    }
+    return dst->str;
+}
+
+static void ini_print_chapter_header(WriterContext *wctx, const char *chapter)
+{
+    INIContext *ini = wctx->priv;
+
+    av_bprint_clear(&ini->chapter_name);
+    av_bprintf(&ini->chapter_name, "%s", chapter);
+
+    if (wctx->nb_chapter)
+        printf("\n");
+    ini->print_packets_and_frames = !strcmp("packets_and_frames", chapter);
+}
+
+static void ini_print_section_header(WriterContext *wctx, const char *section)
+{
+    INIContext *ini = wctx->priv;
+    int n;
+    if (wctx->nb_section)
+        printf("\n");
+    av_bprint_clear(&ini->section_name);
+
+    if (ini->hierarchical && wctx->multiple_sections)
+        av_bprintf(&ini->section_name, "%s.", ini->chapter_name.str);
+    av_bprintf(&ini->section_name, "%s", section);
+
+    if (ini->print_packets_and_frames)
+        n = !strcmp(section, "packet") ? ini->nb_packet++ : ini->nb_frame++;
+    else
+        n = wctx->nb_section;
+    if (wctx->multiple_sections)
+        av_bprintf(&ini->section_name, ".%d", n);
+    printf("[%s]\n", ini->section_name.str);
+}
+
+static void ini_print_str(WriterContext *wctx, const char *key, const char *value)
+{
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    printf("%s=", ini_escape_str(&buf, key));
+    av_bprint_clear(&buf);
+    printf("%s\n", ini_escape_str(&buf, value));
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void ini_print_int(WriterContext *wctx, const char *key, long long int value)
+{
+    printf("%s=%lld\n", key, value);
+}
+
+static void ini_show_tags(WriterContext *wctx, AVDictionary *dict)
+{
+    INIContext *ini = wctx->priv;
+    AVDictionaryEntry *tag = NULL;
+    int is_first = 1;
+
+    while ((tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX))) {
+        if (is_first) {
+            printf("\n[%s.tags]\n", ini->section_name.str);
+            is_first = 0;
+        }
+        writer_print_string(wctx, tag->key, tag->value, 0);
+    }
+}
+
+static const Writer ini_writer = {
+    .name                  = "ini",
+    .priv_size             = sizeof(INIContext),
+    .init                  = ini_init,
+    .uninit                = ini_uninit,
+    .print_header          = ini_print_header,
+    .print_chapter_header  = ini_print_chapter_header,
+    .print_section_header  = ini_print_section_header,
+    .print_integer         = ini_print_int,
+    .print_string          = ini_print_str,
+    .show_tags             = ini_show_tags,
+    .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS|WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
+};
+
 /* JSON output */
 
 typedef struct {
@@ -1168,6 +1336,7 @@ static void writer_register_all(void)
     writer_register(&default_writer);
     writer_register(&compact_writer);
     writer_register(&csv_writer);
+    writer_register(&ini_writer);
     writer_register(&json_writer);
     writer_register(&xml_writer);
 }
@@ -1734,7 +1903,7 @@ static const OptionDef options[] = {
     { "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, compact, csv, json, xml)", "format" },
+      "set the output printing format (available formats are: default, compact, csv, ini, json, xml)", "format" },
     { "show_error",   OPT_BOOL, {(void*)&do_show_error} ,  "show probing error" },
     { "show_format",  OPT_BOOL, {(void*)&do_show_format} , "show format/container info" },
     { "show_frames",  OPT_BOOL, {(void*)&do_show_frames} , "show frames info" },