+ av_dict_free(&stream_entries_to_show);
+}
+
+/*
+ * The output is structured in array and objects that might contain items
+ * Array could require the objects within to not be named.
+ * Object could require the items within to be named.
+ *
+ * For flat representation the name of each section is saved on prefix so it
+ * can be rendered in order to represent nested structures (e.g. array of
+ * objects for the packets list).
+ *
+ * Within an array each element can need an unique identifier or an index.
+ *
+ * Nesting level is accounted separately.
+ */
+
+typedef enum {
+ ARRAY,
+ OBJECT
+} PrintElementType;
+
+typedef struct PrintElement {
+ const char *name;
+ PrintElementType type;
+ int64_t index;
+ int64_t nb_elems;
+} PrintElement;
+
+typedef struct PrintContext {
+ PrintElement *prefix;
+ int level;
+ void (*print_header)(void);
+ void (*print_footer)(void);
+
+ void (*print_array_header) (const char *name, int plain_values);
+ void (*print_array_footer) (const char *name, int plain_values);
+ void (*print_object_header)(const char *name);
+ void (*print_object_footer)(const char *name);
+
+ void (*print_integer) (const char *key, int64_t value);
+ void (*print_string) (const char *key, const char *value);
+} PrintContext;
+
+static AVIOContext *probe_out = NULL;
+static PrintContext octx;
+#define AVP_INDENT() avio_printf(probe_out, "%*c", octx.level * 2, ' ')
+
+/*
+ * Default format, INI
+ *
+ * - all key and values are utf8
+ * - '.' is the subgroup separator
+ * - newlines and the following characters are escaped
+ * - '\' is the escape character
+ * - '#' is the comment
+ * - '=' is the key/value separators
+ * - ':' is not used but usually parsed as key/value separator
+ */
+
+static void ini_print_header(void)
+{
+ avio_printf(probe_out, "# avprobe output\n\n");
+}
+static void ini_print_footer(void)
+{
+ avio_w8(probe_out, '\n');
+}
+
+static void ini_escape_print(const char *s)
+{
+ int i = 0;
+ char c = 0;
+
+ while (c = s[i++]) {
+ switch (c) {
+ case '\r': avio_printf(probe_out, "%s", "\\r"); break;
+ case '\n': avio_printf(probe_out, "%s", "\\n"); break;
+ case '\f': avio_printf(probe_out, "%s", "\\f"); break;
+ case '\b': avio_printf(probe_out, "%s", "\\b"); break;
+ case '\t': avio_printf(probe_out, "%s", "\\t"); break;
+ case '\\':
+ case '#' :
+ case '=' :
+ case ':' : avio_w8(probe_out, '\\');
+ default:
+ if ((unsigned char)c < 32)
+ avio_printf(probe_out, "\\x00%02x", c & 0xff);
+ else
+ avio_w8(probe_out, c);
+ break;
+ }
+ }
+}
+
+static void ini_print_array_header(const char *name, int plain_values)
+{
+ if (!plain_values) {
+ /* Add a new line if we create a new full group */
+ if (octx.prefix[octx.level -1].nb_elems)
+ avio_printf(probe_out, "\n");
+ } else {
+ ini_escape_print(name);
+ avio_w8(probe_out, '=');
+ }
+}
+
+static void ini_print_array_footer(const char *name, int plain_values)
+{
+ if (plain_values)
+ avio_printf(probe_out, "\n");
+}
+
+static void ini_print_object_header(const char *name)
+{
+ int i;
+ PrintElement *el = octx.prefix + octx.level -1;
+
+ if (el->nb_elems)
+ avio_printf(probe_out, "\n");
+
+ avio_printf(probe_out, "[");
+
+ for (i = 1; i < octx.level; i++) {
+ el = octx.prefix + i;
+ avio_printf(probe_out, "%s.", el->name);
+ if (el->index >= 0)
+ avio_printf(probe_out, "%"PRId64".", el->index);
+ }
+
+ avio_printf(probe_out, "%s", name);
+ if (el->type == ARRAY)
+ avio_printf(probe_out, ".%"PRId64"", el->nb_elems);
+ avio_printf(probe_out, "]\n");
+}
+
+static void ini_print_integer(const char *key, int64_t value)
+{
+ if (key) {
+ ini_escape_print(key);
+ avio_printf(probe_out, "=%"PRId64"\n", value);
+ } else {
+ if (octx.prefix[octx.level -1].nb_elems)
+ avio_printf(probe_out, ",");
+ avio_printf(probe_out, "%"PRId64, value);
+ }
+}
+
+
+static void ini_print_string(const char *key, const char *value)
+{
+ ini_escape_print(key);
+ avio_printf(probe_out, "=");
+ ini_escape_print(value);
+ avio_w8(probe_out, '\n');
+}
+
+/*
+ * Alternate format, JSON
+ */
+
+static void json_print_header(void)
+{
+ avio_printf(probe_out, "{");
+}
+static void json_print_footer(void)
+{
+ avio_printf(probe_out, "}\n");
+}
+
+static void json_print_array_header(const char *name, int plain_values)
+{
+ if (octx.prefix[octx.level -1].nb_elems)
+ avio_printf(probe_out, ",\n");
+ AVP_INDENT();
+ avio_printf(probe_out, "\"%s\" : ", name);
+ avio_printf(probe_out, "[\n");
+}
+
+static void json_print_array_footer(const char *name, int plain_values)
+{
+ avio_printf(probe_out, "\n");
+ AVP_INDENT();
+ avio_printf(probe_out, "]");
+}
+
+static void json_print_object_header(const char *name)
+{
+ if (octx.prefix[octx.level -1].nb_elems)
+ avio_printf(probe_out, ",\n");
+ AVP_INDENT();
+ if (octx.prefix[octx.level -1].type == OBJECT)
+ avio_printf(probe_out, "\"%s\" : ", name);
+ avio_printf(probe_out, "{\n");
+}
+
+static void json_print_object_footer(const char *name)
+{
+ avio_printf(probe_out, "\n");
+ AVP_INDENT();
+ avio_printf(probe_out, "}");
+}
+
+static void json_print_integer(const char *key, int64_t value)
+{
+ if (key) {
+ if (octx.prefix[octx.level -1].nb_elems)
+ avio_printf(probe_out, ",\n");
+ AVP_INDENT();
+ avio_printf(probe_out, "\"%s\" : ", key);
+ } else {
+ if (octx.prefix[octx.level -1].nb_elems)
+ avio_printf(probe_out, ", ");
+ else
+ AVP_INDENT();
+ }
+ avio_printf(probe_out, "%"PRId64, value);
+}
+
+static void json_escape_print(const char *s)
+{
+ int i = 0;
+ char c = 0;
+
+ while (c = s[i++]) {
+ switch (c) {
+ case '\r': avio_printf(probe_out, "%s", "\\r"); break;
+ case '\n': avio_printf(probe_out, "%s", "\\n"); break;
+ case '\f': avio_printf(probe_out, "%s", "\\f"); break;
+ case '\b': avio_printf(probe_out, "%s", "\\b"); break;
+ case '\t': avio_printf(probe_out, "%s", "\\t"); break;
+ case '\\':
+ case '"' : avio_w8(probe_out, '\\');
+ default:
+ if ((unsigned char)c < 32)
+ avio_printf(probe_out, "\\u00%02x", c & 0xff);
+ else
+ avio_w8(probe_out, c);
+ break;
+ }
+ }
+}
+
+static void json_print_string(const char *key, const char *value)
+{
+ if (octx.prefix[octx.level -1].nb_elems)
+ avio_printf(probe_out, ",\n");
+ AVP_INDENT();
+ avio_w8(probe_out, '\"');
+ json_escape_print(key);
+ avio_printf(probe_out, "\" : \"");
+ json_escape_print(value);
+ avio_w8(probe_out, '\"');
+}
+
+/*
+ * old-style pseudo-INI
+ */
+static void old_print_object_header(const char *name)
+{
+ char *str, *p;
+
+ if (!strcmp(name, "tags"))
+ return;
+
+ str = p = av_strdup(name);
+ if (!str)
+ return;
+ while (*p) {
+ *p = av_toupper(*p);
+ p++;
+ }
+
+ avio_printf(probe_out, "[%s]\n", str);
+ av_freep(&str);
+}
+
+static void old_print_object_footer(const char *name)
+{
+ char *str, *p;
+
+ if (!strcmp(name, "tags"))
+ return;
+
+ str = p = av_strdup(name);
+ if (!str)
+ return;
+ while (*p) {
+ *p = av_toupper(*p);
+ p++;
+ }
+
+ avio_printf(probe_out, "[/%s]\n", str);
+ av_freep(&str);
+}
+
+static void old_print_string(const char *key, const char *value)
+{
+ if (!strcmp(octx.prefix[octx.level - 1].name, "tags"))
+ avio_printf(probe_out, "TAG:");
+ ini_print_string(key, value);
+}
+
+/*
+ * Simple Formatter for single entries.
+ */
+
+static void show_format_entry_integer(const char *key, int64_t value)
+{
+ if (key && av_dict_get(fmt_entries_to_show, key, NULL, 0)) {
+ if (nb_fmt_entries_to_show > 1)
+ avio_printf(probe_out, "%s=", key);
+ avio_printf(probe_out, "%"PRId64"\n", value);
+ }
+}
+
+static void show_format_entry_string(const char *key, const char *value)
+{
+ if (key && av_dict_get(fmt_entries_to_show, key, NULL, 0)) {
+ if (nb_fmt_entries_to_show > 1)
+ avio_printf(probe_out, "%s=", key);
+ avio_printf(probe_out, "%s\n", value);
+ }
+}
+
+static void show_stream_entry_header(const char *key, int value)
+{
+ header_key = key;
+}
+
+static void show_stream_entry_footer(const char *key, int value)
+{
+ header_key = NULL;
+}
+
+static void show_stream_entry_integer(const char *key, int64_t value)
+{
+ if (!key)
+ key = header_key;
+
+ if (key && av_dict_get(stream_entries_to_show, key, NULL, 0)) {
+ if (nb_stream_entries_to_show > 1)
+ avio_printf(probe_out, "%s=", key);
+ avio_printf(probe_out, "%"PRId64"\n", value);
+ }
+}
+
+static void show_stream_entry_string(const char *key, const char *value)
+{
+ if (key && av_dict_get(stream_entries_to_show, key, NULL, 0)) {
+ if (nb_stream_entries_to_show > 1)
+ avio_printf(probe_out, "%s=", key);
+ avio_printf(probe_out, "%s\n", value);
+ }
+}
+
+static void probe_group_enter(const char *name, int type)
+{
+ int64_t count = -1;
+
+ octx.prefix =
+ av_realloc(octx.prefix, sizeof(PrintElement) * (octx.level + 1));
+
+ if (!octx.prefix || !name) {
+ fprintf(stderr, "Out of memory\n");
+ exit_program(1);
+ }
+
+ if (octx.level) {
+ PrintElement *parent = octx.prefix + octx.level -1;
+ if (parent->type == ARRAY)
+ count = parent->nb_elems;
+ parent->nb_elems++;
+ }
+
+ octx.prefix[octx.level++] = (PrintElement){name, type, count, 0};
+}
+
+static void probe_group_leave(void)
+{
+ --octx.level;
+}
+
+static void probe_header(void)
+{
+ if (octx.print_header)
+ octx.print_header();
+ probe_group_enter("root", OBJECT);
+}
+
+static void probe_footer(void)
+{
+ if (octx.print_footer)
+ octx.print_footer();
+ probe_group_leave();
+}
+
+
+static void probe_array_header(const char *name, int plain_values)
+{
+ if (octx.print_array_header)
+ octx.print_array_header(name, plain_values);
+
+ probe_group_enter(name, ARRAY);
+}
+
+static void probe_array_footer(const char *name, int plain_values)
+{
+ probe_group_leave();
+ if (octx.print_array_footer)
+ octx.print_array_footer(name, plain_values);
+}
+
+static void probe_object_header(const char *name)
+{
+ if (octx.print_object_header)
+ octx.print_object_header(name);
+
+ probe_group_enter(name, OBJECT);
+}
+
+static void probe_object_footer(const char *name)
+{
+ probe_group_leave();
+ if (octx.print_object_footer)
+ octx.print_object_footer(name);
+}
+
+static void probe_int(const char *key, int64_t value)
+{
+ octx.print_integer(key, value);
+ octx.prefix[octx.level -1].nb_elems++;
+}
+
+static void probe_str(const char *key, const char *value)
+{
+ octx.print_string(key, value);
+ octx.prefix[octx.level -1].nb_elems++;
+}
+
+static void probe_dict(AVDictionary *dict, const char *name)
+{
+ AVDictionaryEntry *entry = NULL;
+ if (!dict)
+ return;
+ probe_object_header(name);
+ while ((entry = av_dict_get(dict, "", entry, AV_DICT_IGNORE_SUFFIX))) {
+ probe_str(entry->key, entry->value);
+ }
+ probe_object_footer(name);