Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: softworkz <ffmpegagent@gmail.com>
To: ffmpeg-devel@ffmpeg.org
Cc: Soft Works <softworkz-at-hotmail.com@ffmpeg.org>,
	softworkz <softworkz@hotmail.com>,
	Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
Subject: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c
Date: Sat, 01 Mar 2025 10:01:58 +0000
Message-ID: <6239813ba0e293bd427cecf8437aadef778ea3ee.1740823324.git.ffmpegagent@gmail.com> (raw)
In-Reply-To: <pull.52.v3.ffstaging.FFmpeg.1740823324.ffmpegagent@gmail.com>

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c  | 671 +++++++++++++++++++++++++++++
 fftools/textformat/avtextformat.h  | 171 ++++++++
 fftools/textformat/avtextwriters.h |  68 +++
 fftools/textformat/tf_compact.c    | 282 ++++++++++++
 fftools/textformat/tf_default.c    | 145 +++++++
 fftools/textformat/tf_flat.c       | 174 ++++++++
 fftools/textformat/tf_ini.c        | 160 +++++++
 fftools/textformat/tf_json.c       | 215 +++++++++
 fftools/textformat/tf_xml.c        | 221 ++++++++++
 fftools/textformat/tw_avio.c       | 129 ++++++
 fftools/textformat/tw_buffer.c     |  92 ++++
 fftools/textformat/tw_stdout.c     |  82 ++++
 12 files changed, 2410 insertions(+)
 create mode 100644 fftools/textformat/avtextformat.c
 create mode 100644 fftools/textformat/avtextformat.h
 create mode 100644 fftools/textformat/avtextwriters.h
 create mode 100644 fftools/textformat/tf_compact.c
 create mode 100644 fftools/textformat/tf_default.c
 create mode 100644 fftools/textformat/tf_flat.c
 create mode 100644 fftools/textformat/tf_ini.c
 create mode 100644 fftools/textformat/tf_json.c
 create mode 100644 fftools/textformat/tf_xml.c
 create mode 100644 fftools/textformat/tw_avio.c
 create mode 100644 fftools/textformat/tw_buffer.c
 create mode 100644 fftools/textformat/tw_stdout.c

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
new file mode 100644
index 0000000000..1fba78b103
--- /dev/null
+++ b/fftools/textformat/avtextformat.c
@@ -0,0 +1,671 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libavutil/mem.h"
+#include "libavutil/avassert.h"
+#include "libavutil/bprint.h"
+#include "libavutil/error.h"
+#include "libavutil/hash.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/macros.h"
+#include "libavutil/opt.h"
+#include "avtextformat.h"
+
+#define SECTION_ID_NONE -1
+
+#define SHOW_OPTIONAL_FIELDS_AUTO       -1
+#define SHOW_OPTIONAL_FIELDS_NEVER       0
+#define SHOW_OPTIONAL_FIELDS_ALWAYS      1
+
+static const struct {
+    double bin_val;
+    double dec_val;
+    const char *bin_str;
+    const char *dec_str;
+} si_prefixes[] = {
+    { 1.0, 1.0, "", "" },
+    { 1.024e3, 1e3, "Ki", "K" },
+    { 1.048576e6, 1e6, "Mi", "M" },
+    { 1.073741824e9, 1e9, "Gi", "G" },
+    { 1.099511627776e12, 1e12, "Ti", "T" },
+    { 1.125899906842624e15, 1e15, "Pi", "P" },
+};
+
+static const char *avtext_context_get_formatter_name(void *p)
+{
+    AVTextFormatContext *tctx = p;
+    return tctx->formatter->name;
+}
+
+#define OFFSET(x) offsetof(AVTextFormatContext, x)
+
+static const AVOption textcontext_options[] = {
+    { "string_validation", "set string validation mode",
+      OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, 0, AV_TEXTFORMAT_STRING_VALIDATION_NB-1, .unit = "sv" },
+    { "sv", "set string validation mode",
+      OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, 0, AV_TEXTFORMAT_STRING_VALIDATION_NB-1, .unit = "sv" },
+        { "ignore",  NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_IGNORE},  .unit = "sv" },
+        { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, .unit = "sv" },
+        { "fail",    NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_FAIL},    .unit = "sv" },
+    { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}},
+    { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str="\xEF\xBF\xBD"}},
+    { NULL }
+};
+
+static void *trextcontext_child_next(void *obj, void *prev)
+{
+    AVTextFormatContext *ctx = obj;
+    if (!prev && ctx->formatter && ctx->formatter->priv_class && ctx->priv)
+        return ctx->priv;
+    return NULL;
+}
+
+static const AVClass textcontext_class = {
+    .class_name = "AVTextContext",
+    .item_name  = avtext_context_get_formatter_name,
+    .option     = textcontext_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+    .child_next = trextcontext_child_next,
+};
+
+static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size)
+{
+    int i;
+    av_bprintf(bp, "0X");
+    for (i = 0; i < ubuf_size; i++)
+        av_bprintf(bp, "%02X", ubuf[i]);
+}
+
+int avtext_context_close(AVTextFormatContext **ptctx)
+{
+    AVTextFormatContext *tctx = *ptctx;
+    int i;
+    int ret = 0;
+
+    if (!tctx)
+        return -1;
+
+    av_hash_freep(&tctx->hash);
+
+    av_hash_freep(&tctx->hash);
+
+    if (tctx->formatter->uninit)
+        tctx->formatter->uninit(tctx);
+    for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
+        av_bprint_finalize(&tctx->section_pbuf[i], NULL);
+    if (tctx->formatter->priv_class)
+        av_opt_free(tctx->priv);
+    av_freep(&tctx->priv);
+    av_opt_free(tctx);
+    av_freep(ptctx);
+    return ret;
+}
+
+
+int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
+                        const struct AVTextFormatSection *sections, int nb_sections,
+                        int show_value_unit,
+                        int use_value_prefix,
+                        int use_byte_value_binary_prefix,
+                        int use_value_sexagesimal_format,
+                        int show_optional_fields,
+                        char *show_data_hash)
+{
+    AVTextFormatContext *tctx;
+    int i, ret = 0;
+
+    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    tctx->show_value_unit = show_value_unit;
+    tctx->use_value_prefix = use_value_prefix;
+    tctx->use_byte_value_binary_prefix = use_byte_value_binary_prefix;
+    tctx->use_value_sexagesimal_format = use_value_sexagesimal_format;
+    tctx->show_optional_fields = show_optional_fields;
+
+    if (nb_sections > SECTION_MAX_NB_SECTIONS) {
+        av_log(tctx, AV_LOG_ERROR, "The number of section definitions (%d) is larger than the maximum allowed (%d)\n", nb_sections, SECTION_MAX_NB_SECTIONS);
+        goto fail;
+    }
+
+    tctx->class = &textcontext_class;
+    tctx->formatter = formatter;
+    tctx->level = -1;
+    tctx->sections = sections;
+    tctx->nb_sections = nb_sections;
+    tctx->writer = writer_context;
+
+    av_opt_set_defaults(tctx);
+
+    if (formatter->priv_class) {
+        void *priv_ctx = tctx->priv;
+        *(const AVClass **)priv_ctx = formatter->priv_class;
+        av_opt_set_defaults(priv_ctx);
+    }
+
+    /* convert options to dictionary */
+    if (args) {
+        AVDictionary *opts = NULL;
+        const AVDictionaryEntry *opt = NULL;
+
+        if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) {
+            av_log(tctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to textformat context\n", args);
+            av_dict_free(&opts);
+            goto fail;
+        }
+
+        while ((opt = av_dict_iterate(opts, opt))) {
+            if ((ret = av_opt_set(tctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) {
+                av_log(tctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to textformat context\n",
+                       opt->key, opt->value);
+                av_dict_free(&opts);
+                goto fail;
+            }
+        }
+
+        av_dict_free(&opts);
+    }
+
+    if (show_data_hash) {
+        if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
+            if (ret == AVERROR(EINVAL)) {
+                const char *n;
+                av_log(NULL, AV_LOG_ERROR, "Unknown hash algorithm '%s'\nKnown algorithms:", show_data_hash);
+                for (i = 0; (n = av_hash_names(i)); i++)
+                    av_log(NULL, AV_LOG_ERROR, " %s", n);
+                av_log(NULL, AV_LOG_ERROR, "\n");
+            }
+            return ret;
+        }
+    }
+
+    /* validate replace string */
+    {
+        const uint8_t *p = tctx->string_validation_replacement;
+        const uint8_t *endp = p + strlen(p);
+        while (*p) {
+            const uint8_t *p0 = p;
+            int32_t code;
+            ret = av_utf8_decode(&code, &p, endp, tctx->string_validation_utf8_flags);
+            if (ret < 0) {
+                AVBPrint bp;
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+                bprint_bytes(&bp, p0, p-p0),
+                    av_log(tctx, AV_LOG_ERROR,
+                           "Invalid UTF8 sequence %s found in string validation replace '%s'\n",
+                           bp.str, tctx->string_validation_replacement);
+                return ret;
+            }
+        }
+    }
+
+    for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
+        av_bprint_init(&tctx->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (tctx->formatter->init)
+        ret = tctx->formatter->init(tctx);
+    if (ret < 0)
+        goto fail;
+
+    *ptctx = tctx;
+
+    return 0;
+
+fail:
+    avtext_context_close(&tctx);
+    return ret;
+}
+
+/* Temporary definitions during refactoring */
+static const char unit_second_str[]         = "s"    ;
+static const char unit_hertz_str[]          = "Hz"   ;
+static const char unit_byte_str[]           = "byte" ;
+static const char unit_bit_per_second_str[] = "bit/s";
+
+
+void avtext_print_section_header(AVTextFormatContext *tctx,
+                                               const void *data,
+                                               int section_id)
+{
+    tctx->level++;
+    av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
+
+    tctx->nb_item[tctx->level] = 0;
+    memset(tctx->nb_item_type[tctx->level], 0, sizeof(tctx->nb_item_type[tctx->level]));
+    tctx->section[tctx->level] = &tctx->sections[section_id];
+
+    if (tctx->formatter->print_section_header)
+        tctx->formatter->print_section_header(tctx, data);
+}
+
+void avtext_print_section_footer(AVTextFormatContext *tctx)
+{
+    int section_id = tctx->section[tctx->level]->id;
+    int parent_section_id = tctx->level ?
+        tctx->section[tctx->level-1]->id : SECTION_ID_NONE;
+
+    if (parent_section_id != SECTION_ID_NONE) {
+        tctx->nb_item[tctx->level - 1]++;
+        tctx->nb_item_type[tctx->level - 1][section_id]++;
+    }
+
+    if (tctx->formatter->print_section_footer)
+        tctx->formatter->print_section_footer(tctx);
+    tctx->level--;
+}
+
+void avtext_print_integer(AVTextFormatContext *tctx,
+                                        const char *key, int64_t val)
+{
+    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+
+    if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) {
+        tctx->formatter->print_integer(tctx, key, val);
+        tctx->nb_item[tctx->level]++;
+    }
+}
+
+static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
+{
+    const uint8_t *p, *endp;
+    AVBPrint dstbuf;
+    int invalid_chars_nb = 0, ret = 0;
+
+    av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    endp = src + strlen(src);
+    for (p = src; *p;) {
+        uint32_t code;
+        int invalid = 0;
+        const uint8_t *p0 = p;
+
+        if (av_utf8_decode(&code, &p, endp, tctx->string_validation_utf8_flags) < 0) {
+            AVBPrint bp;
+            av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+            bprint_bytes(&bp, p0, p-p0);
+            av_log(tctx, AV_LOG_DEBUG,
+                   "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src);
+            invalid = 1;
+        }
+
+        if (invalid) {
+            invalid_chars_nb++;
+
+            switch (tctx->string_validation) {
+            case AV_TEXTFORMAT_STRING_VALIDATION_FAIL:
+                av_log(tctx, AV_LOG_ERROR,
+                       "Invalid UTF-8 sequence found in string '%s'\n", src);
+                ret = AVERROR_INVALIDDATA;
+                goto end;
+                break;
+
+            case AV_TEXTFORMAT_STRING_VALIDATION_REPLACE:
+                av_bprintf(&dstbuf, "%s", tctx->string_validation_replacement);
+                break;
+            }
+        }
+
+        if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
+            av_bprint_append_data(&dstbuf, p0, p-p0);
+    }
+
+    if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
+        av_log(tctx, AV_LOG_WARNING,
+               "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n",
+               invalid_chars_nb, src, tctx->string_validation_replacement);
+    }
+
+end:
+    av_bprint_finalize(&dstbuf, dstp);
+    return ret;
+}
+
+struct unit_value {
+    union { double d; int64_t i; } val;
+    const char *unit;
+};
+
+static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
+{
+    double vald;
+    int64_t vali;
+    int show_float = 0;
+
+    if (uv.unit == unit_second_str) {
+        vald = uv.val.d;
+        show_float = 1;
+    } else {
+        vald = vali = uv.val.i;
+    }
+
+    if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
+        double secs;
+        int hours, mins;
+        secs  = vald;
+        mins  = (int)secs / 60;
+        secs  = secs - mins * 60;
+        hours = mins / 60;
+        mins %= 60;
+        snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs);
+    } else {
+        const char *prefix_string = "";
+
+        if (tctx->use_value_prefix && vald > 1) {
+            int64_t index;
+
+            if (uv.unit == unit_byte_str && tctx->use_byte_value_binary_prefix) {
+                index = (int64_t) (log2(vald)) / 10;
+                index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
+                vald /= si_prefixes[index].bin_val;
+                prefix_string = si_prefixes[index].bin_str;
+            } else {
+                index = (int64_t) (log10(vald)) / 3;
+                index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
+                vald /= si_prefixes[index].dec_val;
+                prefix_string = si_prefixes[index].dec_str;
+            }
+            vali = vald;
+        }
+
+        if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
+            snprintf(buf, buf_size, "%f", vald);
+        else
+            snprintf(buf, buf_size, "%"PRId64, vali);
+        av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || tctx->show_value_unit ? " " : "",
+                 prefix_string, tctx->show_value_unit ? uv.unit : "");
+    }
+
+    return buf;
+}
+
+
+void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit)
+{
+    char val_str[128];
+    struct unit_value uv;
+    uv.val.i = value;
+    uv.unit = unit;
+    avtext_print_string(tctx, key, value_string(tctx, val_str, sizeof(val_str), uv), 0);
+}
+
+
+int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
+{
+    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    int ret = 0;
+
+    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
+        (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
+        && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
+        && !(tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS)))
+        return 0;
+
+    if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) {
+        if (flags & AV_TEXTFORMAT_PRINT_STRING_VALIDATE) {
+            char *key1 = NULL, *val1 = NULL;
+            ret = validate_string(tctx, &key1, key);
+            if (ret < 0) goto end;
+            ret = validate_string(tctx, &val1, val);
+            if (ret < 0) goto end;
+            tctx->formatter->print_string(tctx, key1, val1);
+        end:
+            if (ret < 0) {
+                av_log(tctx, AV_LOG_ERROR,
+                       "Invalid key=value string combination %s=%s in section %s\n",
+                       key, val, section->unique_name);
+            }
+            av_free(key1);
+            av_free(val1);
+        } else {
+            tctx->formatter->print_string(tctx, key, val);
+        }
+
+        tctx->nb_item[tctx->level]++;
+    }
+
+    return ret;
+}
+
+void avtext_print_rational(AVTextFormatContext *tctx,
+                                         const char *key, AVRational q, char sep)
+{
+    AVBPrint buf;
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+    av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
+    avtext_print_string(tctx, key, buf.str, 0);
+}
+
+void avtext_print_time(AVTextFormatContext *tctx, const char *key,
+                              int64_t ts, const AVRational *time_base, int is_duration)
+{
+    char buf[128];
+
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
+        avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
+    } else {
+        double d = ts * av_q2d(*time_base);
+        struct unit_value uv;
+        uv.val.d = d;
+        uv.unit = unit_second_str;
+        value_string(tctx, buf, sizeof(buf), uv);
+        avtext_print_string(tctx, key, buf, 0);
+    }
+}
+
+void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration)
+{
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
+        avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
+    } else {
+        avtext_print_integer(tctx, key, ts);
+    }
+}
+
+void avtext_print_data(AVTextFormatContext *tctx, const char *name,
+                              const uint8_t *data, int size)
+{
+    AVBPrint bp;
+    int offset = 0, l, i;
+
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&bp, "\n");
+    while (size) {
+        av_bprintf(&bp, "%08x: ", offset);
+        l = FFMIN(size, 16);
+        for (i = 0; i < l; i++) {
+            av_bprintf(&bp, "%02x", data[i]);
+            if (i & 1)
+                av_bprintf(&bp, " ");
+        }
+        av_bprint_chars(&bp, ' ', 41 - 2 * i - i / 2);
+        for (i = 0; i < l; i++)
+            av_bprint_chars(&bp, data[i] - 32U < 95 ? data[i] : '.', 1);
+        av_bprintf(&bp, "\n");
+        offset += l;
+        data   += l;
+        size   -= l;
+    }
+    avtext_print_string(tctx, name, bp.str, 0);
+    av_bprint_finalize(&bp, NULL);
+}
+
+void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
+                                   const uint8_t *data, int size)
+{
+    char *p, buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
+
+    if (!tctx->hash)
+        return;
+    av_hash_init(tctx->hash);
+    av_hash_update(tctx->hash, data, size);
+    snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
+    p = buf + strlen(buf);
+    av_hash_final_hex(tctx->hash, p, buf + sizeof(buf) - p);
+    avtext_print_string(tctx, name, buf, 0);
+}
+
+void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
+                                  uint8_t *data, int size, const char *format,
+                                  int columns, int bytes, int offset_add)
+{
+    AVBPrint bp;
+    int offset = 0, l, i;
+
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&bp, "\n");
+    while (size) {
+        av_bprintf(&bp, "%08x: ", offset);
+        l = FFMIN(size, columns);
+        for (i = 0; i < l; i++) {
+            if      (bytes == 1) av_bprintf(&bp, format, *data);
+            else if (bytes == 2) av_bprintf(&bp, format, AV_RN16(data));
+            else if (bytes == 4) av_bprintf(&bp, format, AV_RN32(data));
+            data += bytes;
+            size --;
+        }
+        av_bprintf(&bp, "\n");
+        offset += offset_add;
+    }
+    avtext_print_string(tctx, name, bp.str, 0);
+    av_bprint_finalize(&bp, NULL);
+}
+
+static const char *avtextwriter_context_get_writer_name(void *p)
+{
+    AVTextWriterContext *wctx = p;
+    return wctx->writer->name;
+}
+
+static void *writercontext_child_next(void *obj, void *prev)
+{
+    AVTextFormatContext *ctx = obj;
+    if (!prev && ctx->formatter && ctx->formatter->priv_class && ctx->priv)
+        return ctx->priv;
+    return NULL;
+}
+
+static const AVClass textwriter_class = {
+    .class_name = "AVTextWriterContext",
+    .item_name  = avtextwriter_context_get_writer_name,
+    .version    = LIBAVUTIL_VERSION_INT,
+    .child_next = writercontext_child_next,
+};
+
+
+int avtextwriter_context_close(AVTextWriterContext **pwctx)
+{
+    AVTextWriterContext *wctx = *pwctx;
+    int ret = 0;
+
+    if (!wctx)
+        return -1;
+
+    if (wctx->writer->uninit)
+        wctx->writer->uninit(wctx);
+    if (wctx->writer->priv_class)
+        av_opt_free(wctx->priv);
+    av_freep(&wctx->priv);
+    av_freep(pwctx);
+    return ret;
+}
+
+
+int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer)
+{
+    AVTextWriterContext *wctx;
+    int ret = 0;
+
+    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    if (writer->priv_class) {
+        void *priv_ctx = wctx->priv;
+        *(const AVClass **)priv_ctx = writer->priv_class;
+        av_opt_set_defaults(priv_ctx);
+    }
+
+    wctx->class = &textwriter_class;
+    wctx->writer = writer;
+
+    av_opt_set_defaults(wctx);
+
+
+    if (wctx->writer->init)
+        ret = wctx->writer->init(wctx);
+    if (ret < 0)
+        goto fail;
+
+    *pwctx = wctx;
+
+    return 0;
+
+fail:
+    avtextwriter_context_close(&wctx);
+    return ret;
+}
+
+static const AVTextFormatter *registered_formatters[7+1];
+static void formatters_register_all(void)
+{
+    static int initialized;
+
+    if (initialized)
+        return;
+    initialized = 1;
+
+    registered_formatters[0] = &avtextformatter_default;
+    registered_formatters[1] = &avtextformatter_compact;
+    registered_formatters[2] = &avtextformatter_csv;
+    registered_formatters[3] = &avtextformatter_flat;
+    registered_formatters[4] = &avtextformatter_ini;
+    registered_formatters[5] = &avtextformatter_json;
+    registered_formatters[6] = &avtextformatter_xml;
+}
+
+const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
+{
+    formatters_register_all();
+
+    for (int i = 0; registered_formatters[i]; i++)
+        if (!strcmp(registered_formatters[i]->name, name))
+            return registered_formatters[i];
+
+    return NULL;
+}
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
new file mode 100644
index 0000000000..b7b6e0eea0
--- /dev/null
+++ b/fftools/textformat/avtextformat.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H
+#define FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include "libavutil/attributes.h"
+#include "libavutil/dict.h"
+#include "libavformat/avio.h"
+#include "libavutil/bprint.h"
+#include "libavutil/rational.h"
+#include "libavutil/hash.h"
+#include "avtextwriters.h"
+
+#define SECTION_MAX_NB_CHILDREN 11
+
+
+struct AVTextFormatSection {
+    int id;             ///< unique id identifying a section
+    const char *name;
+
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER      1 ///< the section only contains other sections, but has no data at its own level
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY        2 ///< the section contains an array of elements of the same type
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys.
+                                           ///  For these sections the element_name field is mandatory.
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE        8 ///< the section contains a type to distinguish multiple nested elements
+#define AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE 16 ///< the items in this array section should be numbered individually by type
+
+    int flags;
+    const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
+    const char *element_name; ///< name of the contained element, if provided
+    const char *unique_name;  ///< unique section name, in case the name is ambiguous
+    AVDictionary *entries_to_show;
+    const char *(* get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined
+    int show_all_entries;
+} AVTextFormatSection;
+
+typedef struct AVTextFormatContext AVTextFormatContext;
+
+#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
+#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
+
+typedef enum {
+    AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
+    AV_TEXTFORMAT_STRING_VALIDATION_REPLACE,
+    AV_TEXTFORMAT_STRING_VALIDATION_IGNORE,
+    AV_TEXTFORMAT_STRING_VALIDATION_NB
+} StringValidation;
+
+typedef struct AVTextFormatter {
+    const AVClass *priv_class;      ///< private class of the formatter, if any
+    int priv_size;                  ///< private size for the formatter context
+    const char *name;
+
+    int  (*init)  (AVTextFormatContext *tctx);
+    void (*uninit)(AVTextFormatContext *tctx);
+
+    void (*print_section_header)(AVTextFormatContext *tctx, const void *data);
+    void (*print_section_footer)(AVTextFormatContext *tctx);
+    void (*print_integer)       (AVTextFormatContext *tctx, const char *, int64_t);
+    void (*print_rational)      (AVTextFormatContext *tctx, AVRational *q, char *sep);
+    void (*print_string)        (AVTextFormatContext *tctx, const char *, const char *);
+    int flags;                  ///< a combination or AV_TEXTFORMAT__FLAG_*
+} AVTextFormatter;
+
+#define SECTION_MAX_NB_LEVELS    12
+#define SECTION_MAX_NB_SECTIONS 100
+
+struct AVTextFormatContext {
+    const AVClass *class;           ///< class of the formatter
+    const AVTextFormatter *formatter;           ///< the AVTextFormatter of which this is an instance
+    AVTextWriterContext *writer;           ///< the AVTextWriterContext 
+
+    char *name;                     ///< name of this formatter instance
+    void *priv;                     ///< private data for use by the filter
+
+    const struct AVTextFormatSection *sections; ///< array containing all sections
+    int nb_sections;                ///< number of sections
+
+    int level;                      ///< current level, starting from 0
+
+    /** number of the item printed in the given section, starting from 0 */
+    unsigned int nb_item[SECTION_MAX_NB_LEVELS];
+    unsigned int nb_item_type[SECTION_MAX_NB_LEVELS][SECTION_MAX_NB_SECTIONS];
+
+    /** section per each level */
+    const struct AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+                                                  ///  used by various formatters
+
+    int show_optional_fields;
+    int show_value_unit;
+    int use_value_prefix;
+    int use_byte_value_binary_prefix;
+    int use_value_sexagesimal_format;
+
+    struct AVHashContext *hash;
+
+    int string_validation;
+    char *string_validation_replacement;
+    unsigned int string_validation_utf8_flags;
+};
+
+#define AV_TEXTFORMAT_PRINT_STRING_OPTIONAL 1
+#define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
+
+int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer, const char *args,
+                        const struct AVTextFormatSection *sections, int nb_sections,
+                        int show_value_unit,
+                        int use_value_prefix,
+                        int use_byte_value_binary_prefix,
+                        int use_value_sexagesimal_format,
+                        int show_optional_fields,
+                        char *show_data_hash);
+
+int avtext_context_close(AVTextFormatContext **tctx);
+
+
+void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id);
+
+void avtext_print_section_footer(AVTextFormatContext *tctx);
+
+void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
+
+int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags);
+
+void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit);
+
+void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep);
+
+void avtext_print_time(AVTextFormatContext *tctx, const char *key, int64_t ts, const AVRational *time_base, int is_duration);
+
+void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration);
+
+void avtext_print_data(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size);
+
+void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size);
+
+void avtext_print_integers(AVTextFormatContext *tctx, const char *name, uint8_t *data, int size, 
+                           const char *format, int columns, int bytes, int offset_add);
+
+const AVTextFormatter *avtext_get_formatter_by_name(const char *name);
+
+extern const AVTextFormatter avtextformatter_default;
+extern const AVTextFormatter avtextformatter_compact;
+extern const AVTextFormatter avtextformatter_csv;
+extern const AVTextFormatter avtextformatter_flat;
+extern const AVTextFormatter avtextformatter_ini;
+extern const AVTextFormatter avtextformatter_json;
+extern const AVTextFormatter avtextformatter_xml;
+
+#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
new file mode 100644
index 0000000000..b344881d05
--- /dev/null
+++ b/fftools/textformat/avtextwriters.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H
+#define FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include "libavutil/attributes.h"
+#include "libavutil/dict.h"
+#include "libavformat/avio.h"
+#include "libavutil/bprint.h"
+#include "libavutil/rational.h"
+#include "libavutil/hash.h"
+
+typedef struct AVTextWriterContext AVTextWriterContext;
+
+typedef struct AVTextWriter {
+    const AVClass *priv_class;      ///< private class of the writer, if any
+    int priv_size;                  ///< private size for the writer private class
+    const char *name;
+
+    int (* init)(AVTextWriterContext *wctx);
+    void (* uninit)(AVTextWriterContext *wctx);
+    void (* writer_w8)(AVTextWriterContext *wctx, int b);
+    void (* writer_put_str)(AVTextWriterContext *wctx, const char *str);
+    void (* writer_printf)(AVTextWriterContext *wctx, const char *fmt, ...);
+} AVTextWriter;
+
+typedef struct AVTextWriterContext {
+    const AVClass *class;            ///< class of the writer
+    const AVTextWriter *writer;
+    const char *name;
+    void *priv;                     ///< private data for use by the writer
+
+} AVTextWriterContext;
+
+
+int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer);
+
+int avtextwriter_context_close(AVTextWriterContext **pwctx);
+
+int avtextwriter_create_stdout(AVTextWriterContext **pwctx);
+
+int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit);
+
+int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit);
+
+int avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer);
+
+#endif /* FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H */
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
new file mode 100644
index 0000000000..ad07ca4bd0
--- /dev/null
+++ b/fftools/textformat/tf_compact.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/error.h>
+#include <libavutil/macros.h>
+#include <libavutil/opt.h>
+
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+
+#define DEFINE_FORMATTER_CLASS(name)                   \
+static const char *name##_get_name(void *ctx)       \
+{                                                   \
+    return #name ;                                  \
+}                                                   \
+static const AVClass name##_class = {               \
+    .class_name = #name,                            \
+    .item_name  = name##_get_name,                  \
+    .option     = name##_options                    \
+}
+
+
+/* Compact output */
+
+/**
+ * Apply C-language-like string escaping.
+ */
+static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
+{
+    const char *p;
+
+    for (p = src; *p; p++) {
+        switch (*p) {
+        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 '\\': av_bprintf(dst, "%s", "\\\\"); break;
+        default:
+            if (*p == sep)
+                av_bprint_chars(dst, '\\', 1);
+            av_bprint_chars(dst, *p, 1);
+        }
+    }
+    return dst->str;
+}
+
+/**
+ * Quote fields containing special characters, check RFC4180.
+ */
+static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
+{
+    char meta_chars[] = { sep, '"', '\n', '\r', '\0' };
+    int needs_quoting = !!src[strcspn(src, meta_chars)];
+
+    if (needs_quoting)
+        av_bprint_chars(dst, '"', 1);
+
+    for (; *src; src++) {
+        if (*src == '"')
+            av_bprint_chars(dst, '"', 1);
+        av_bprint_chars(dst, *src, 1);
+    }
+    if (needs_quoting)
+        av_bprint_chars(dst, '"', 1);
+    return dst->str;
+}
+
+static const char *none_escape_str(AVBPrint *dst, 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;
+    int print_section;
+    char *escape_mode_str;
+    const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx);
+    int nested_section[SECTION_MAX_NB_LEVELS];
+    int has_nested_elems[SECTION_MAX_NB_LEVELS];
+    int terminate_line[SECTION_MAX_NB_LEVELS];
+} CompactContext;
+
+#undef OFFSET
+#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="|"},  0, 0 },
+    {"s",        "set item separator",    OFFSET(item_sep_str),    AV_OPT_TYPE_STRING, {.str="|"},  0, 0 },
+    {"nokey",    "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_BOOL,   {.i64=0},    0,        1        },
+    {"nk",       "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_BOOL,   {.i64=0},    0,        1        },
+    {"escape",   "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"},  0, 0 },
+    {"e",        "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"},  0, 0 },
+    {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL,   {.i64=1},    0,        1        },
+    {"p",             "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL,   {.i64=1},    0,        1        },
+    {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(compact);
+
+static av_cold int compact_init(AVTextFormatContext *wctx)
+{
+    CompactContext *compact = wctx->priv;
+
+    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];
+
+    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 void compact_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+    CompactContext *compact = wctx->priv;
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const struct AVTextFormatSection *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+    compact->terminate_line[wctx->level] = 1;
+    compact->has_nested_elems[wctx->level] = 0;
+
+    av_bprint_clear(&wctx->section_pbuf[wctx->level]);
+    if (parent_section &&
+        (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ||
+         (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) &&
+          !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) {
+
+        /* define a prefix for elements not contained in an array or
+           in a wrapper, or for array elements with a type */
+        const char *element_name = (char *)av_x_if_null(section->element_name, section->name);
+        AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
+
+        compact->nested_section[wctx->level] = 1;
+        compact->has_nested_elems[wctx->level-1] = 1;
+
+        av_bprintf(section_pbuf, "%s%s",
+                   wctx->section_pbuf[wctx->level-1].str, element_name);
+
+        if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
+            // add /TYPE to prefix
+            av_bprint_chars(section_pbuf, '/', 1);
+
+            // normalize section type, replace special characters and lower case
+            for (const char *p = section->get_type(data); *p; p++) {
+                char c =
+                    (*p >= '0' && *p <= '9') ||
+                    (*p >= 'a' && *p <= 'z') ||
+                    (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
+                av_bprint_chars(section_pbuf, c, 1);
+            }
+        }
+        av_bprint_chars(section_pbuf, ':', 1);
+
+        wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1];
+    } else {
+        if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) &&
+            wctx->level && wctx->nb_item[wctx->level-1])
+            writer_w8(wctx, compact->item_sep);
+        if (compact->print_section &&
+            !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
+            writer_printf(wctx, "%s%c", section->name, compact->item_sep);
+    }
+}
+
+static void compact_print_section_footer(AVTextFormatContext *wctx)
+{
+    CompactContext *compact = wctx->priv;
+
+    if (!compact->nested_section[wctx->level] &&
+        compact->terminate_line[wctx->level] &&
+        !(wctx->section[wctx->level]->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
+        writer_w8(wctx, '\n');
+}
+
+static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+    CompactContext *compact = wctx->priv;
+    AVBPrint buf;
+
+    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    if (!compact->nokey)
+        writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx));
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+    CompactContext *compact = wctx->priv;
+
+    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    if (!compact->nokey)
+        writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+    writer_printf(wctx, "%"PRId64, value);
+}
+
+const AVTextFormatter avtextformatter_compact = {
+    .name                 = "compact",
+    .priv_size            = sizeof(CompactContext),
+    .init                 = compact_init,
+    .print_section_header = compact_print_section_header,
+    .print_section_footer = compact_print_section_footer,
+    .print_integer        = compact_print_int,
+    .print_string         = compact_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
+    .priv_class           = &compact_class,
+};
+
+/* CSV output */
+
+#undef OFFSET
+#define OFFSET(x) offsetof(CompactContext, x)
+
+static const AVOption csv_options[] = {
+    {"item_sep", "set item separator",    OFFSET(item_sep_str),    AV_OPT_TYPE_STRING, {.str=","},  0, 0 },
+    {"s",        "set item separator",    OFFSET(item_sep_str),    AV_OPT_TYPE_STRING, {.str=","},  0, 0 },
+    {"nokey",    "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_BOOL,   {.i64=1},    0,        1        },
+    {"nk",       "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_BOOL,   {.i64=1},    0,        1        },
+    {"escape",   "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
+    {"e",        "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
+    {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL,   {.i64=1},    0,        1        },
+    {"p",             "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL,   {.i64=1},    0,        1        },
+    {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(csv);
+
+const AVTextFormatter avtextformatter_csv = {
+    .name                 = "csv",
+    .priv_size            = sizeof(CompactContext),
+    .init                 = compact_init,
+    .print_section_header = compact_print_section_header,
+    .print_section_footer = compact_print_section_footer,
+    .print_integer        = compact_print_int,
+    .print_string         = compact_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
+    .priv_class           = &csv_class,
+};
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
new file mode 100644
index 0000000000..9625dd813b
--- /dev/null
+++ b/fftools/textformat/tf_default.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_CLASS(name)                   \
+static const char *name##_get_name(void *ctx)       \
+{                                                   \
+    return #name ;                                  \
+}                                                   \
+static const AVClass name##_class = {               \
+    .class_name = #name,                            \
+    .item_name  = name##_get_name,                  \
+    .option     = name##_options                    \
+}
+
+/* Default output */
+
+typedef struct DefaultContext {
+    const AVClass *class;
+    int nokey;
+    int noprint_wrappers;
+    int nested_section[SECTION_MAX_NB_LEVELS];
+} DefaultContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(DefaultContext, x)
+
+static const AVOption default_options[] = {
+    { "noprint_wrappers", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "nw",               "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "nokey",          "force no key printing",     OFFSET(nokey),          AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "nk",             "force no key printing",     OFFSET(nokey),          AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(default);
+
+/* lame uppercasing routine, assumes the string is lower case ASCII */
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    int i;
+    for (i = 0; src[i] && i < dst_size-1; i++)
+        dst[i] = av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static void default_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+    DefaultContext *def = wctx->priv;
+    char buf[32];
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const struct AVTextFormatSection *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    av_bprint_clear(&wctx->section_pbuf[wctx->level]);
+    if (parent_section &&
+        !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) {
+        def->nested_section[wctx->level] = 1;
+        av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:",
+                   wctx->section_pbuf[wctx->level-1].str,
+                   upcase_string(buf, sizeof(buf),
+                                 av_x_if_null(section->element_name, section->name)));
+    }
+
+    if (def->noprint_wrappers || def->nested_section[wctx->level])
+        return;
+
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
+        writer_printf(wctx, "[%s]\n", upcase_string(buf, sizeof(buf), section->name));
+}
+
+static void default_print_section_footer(AVTextFormatContext *wctx)
+{
+    DefaultContext *def = wctx->priv;
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    char buf[32];
+
+    if (def->noprint_wrappers || def->nested_section[wctx->level])
+        return;
+
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
+        writer_printf(wctx, "[/%s]\n", upcase_string(buf, sizeof(buf), section->name));
+}
+
+static void default_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+    DefaultContext *def = wctx->priv;
+
+    if (!def->nokey)
+        writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+    writer_printf(wctx, "%s\n", value);
+}
+
+static void default_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+    DefaultContext *def = wctx->priv;
+
+    if (!def->nokey)
+        writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+    writer_printf(wctx, "%"PRId64"\n", value);
+}
+
+const AVTextFormatter avtextformatter_default = {
+    .name                  = "default",
+    .priv_size             = sizeof(DefaultContext),
+    .print_section_header  = default_print_section_header,
+    .print_section_footer  = default_print_section_footer,
+    .print_integer         = default_print_int,
+    .print_string          = default_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
+    .priv_class            = &default_class,
+};
\ No newline at end of file
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
new file mode 100644
index 0000000000..afdc494aee
--- /dev/null
+++ b/fftools/textformat/tf_flat.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/error.h>
+#include <libavutil/macros.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_CLASS(name)                   \
+static const char *name##_get_name(void *ctx)       \
+{                                                   \
+    return #name ;                                  \
+}                                                   \
+static const AVClass name##_class = {               \
+    .class_name = #name,                            \
+    .item_name  = name##_get_name,                  \
+    .option     = name##_options                    \
+}
+
+
+/* Flat output */
+
+typedef struct FlatContext {
+    const AVClass *class;
+    const char *sep_str;
+    char sep;
+    int hierarchical;
+} FlatContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(FlatContext, x)
+
+static const AVOption flat_options[]= {
+    {"sep_char", "set separator",    OFFSET(sep_str),    AV_OPT_TYPE_STRING, {.str="."},  0, 0 },
+    {"s",        "set separator",    OFFSET(sep_str),    AV_OPT_TYPE_STRING, {.str="."},  0, 0 },
+    {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+    {"h",            "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+    {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(flat);
+
+static av_cold int flat_init(AVTextFormatContext *wctx)
+{
+    FlatContext *flat = wctx->priv;
+
+    if (strlen(flat->sep_str) != 1) {
+        av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
+               flat->sep_str);
+        return AVERROR(EINVAL);
+    }
+    flat->sep = flat->sep_str[0];
+
+    return 0;
+}
+
+static const char *flat_escape_key_str(AVBPrint *dst, const char *src, const char sep)
+{
+    const char *p;
+
+    for (p = src; *p; p++) {
+        if (!((*p >= '0' && *p <= '9') ||
+              (*p >= 'a' && *p <= 'z') ||
+              (*p >= 'A' && *p <= 'Z')))
+            av_bprint_chars(dst, '_', 1);
+        else
+            av_bprint_chars(dst, *p, 1);
+    }
+    return dst->str;
+}
+
+static const char *flat_escape_value_str(AVBPrint *dst, const char *src)
+{
+    const char *p;
+
+    for (p = src; *p; p++) {
+        switch (*p) {
+        case '\n': av_bprintf(dst, "%s", "\\n");  break;
+        case '\r': av_bprintf(dst, "%s", "\\r");  break;
+        case '\\': av_bprintf(dst, "%s", "\\\\"); break;
+        case '"':  av_bprintf(dst, "%s", "\\\""); break;
+        case '`':  av_bprintf(dst, "%s", "\\`");  break;
+        case '$':  av_bprintf(dst, "%s", "\\$");  break;
+        default:   av_bprint_chars(dst, *p, 1);   break;
+        }
+    }
+    return dst->str;
+}
+
+static void flat_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+    FlatContext *flat = wctx->priv;
+    AVBPrint *buf = &wctx->section_pbuf[wctx->level];
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const struct AVTextFormatSection *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    /* build section header */
+    av_bprint_clear(buf);
+    if (!parent_section)
+        return;
+    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+
+    if (flat->hierarchical ||
+        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) {
+        av_bprintf(buf, "%s%s", wctx->section[wctx->level]->name, flat->sep_str);
+
+        if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
+            int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ?
+                wctx->nb_item_type[wctx->level-1][section->id] :
+                wctx->nb_item[wctx->level-1];
+            av_bprintf(buf, "%d%s", n, flat->sep_str);
+        }
+    }
+}
+
+static void flat_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+    writer_printf(wctx, "%s%s=%"PRId64"\n", wctx->section_pbuf[wctx->level].str, key, value);
+}
+
+static void flat_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+    FlatContext *flat = wctx->priv;
+    AVBPrint buf;
+
+    writer_put_str(wctx, wctx->section_pbuf[wctx->level].str);
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    writer_printf(wctx, "%s=", flat_escape_key_str(&buf, key, flat->sep));
+    av_bprint_clear(&buf);
+    writer_printf(wctx, "\"%s\"\n", flat_escape_value_str(&buf, value));
+    av_bprint_finalize(&buf, NULL);
+}
+
+const AVTextFormatter avtextformatter_flat = {
+    .name                  = "flat",
+    .priv_size             = sizeof(FlatContext),
+    .init                  = flat_init,
+    .print_section_header  = flat_print_section_header,
+    .print_integer         = flat_print_int,
+    .print_string          = flat_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
+    .priv_class            = &flat_class,
+};
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
new file mode 100644
index 0000000000..99a9af5690
--- /dev/null
+++ b/fftools/textformat/tf_ini.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_CLASS(name)                   \
+static const char *name##_get_name(void *ctx)       \
+{                                                   \
+    return #name ;                                  \
+}                                                   \
+static const AVClass name##_class = {               \
+    .class_name = #name,                            \
+    .item_name  = name##_get_name,                  \
+    .option     = name##_options                    \
+}
+
+/* Default output */
+
+typedef struct DefaultContext {
+    const AVClass *class;
+    int nokey;
+    int noprint_wrappers;
+    int nested_section[SECTION_MAX_NB_LEVELS];
+} DefaultContext;
+
+/* INI format output */
+
+typedef struct INIContext {
+    const AVClass *class;
+    int hierarchical;
+} INIContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(INIContext, x)
+
+static const AVOption ini_options[] = {
+    {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+    {"h",            "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
+    {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(ini);
+
+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_section_header(AVTextFormatContext *wctx, const void *data)
+{
+    INIContext *ini = wctx->priv;
+    AVBPrint *buf = &wctx->section_pbuf[wctx->level];
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const struct AVTextFormatSection *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    av_bprint_clear(buf);
+    if (!parent_section) {
+        writer_put_str(wctx, "# ffprobe output\n\n");
+        return;
+    }
+
+    if (wctx->nb_item[wctx->level-1])
+        writer_w8(wctx, '\n');
+
+    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+    if (ini->hierarchical ||
+        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) {
+        av_bprintf(buf, "%s%s", buf->str[0] ? "." : "", wctx->section[wctx->level]->name);
+
+        if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
+            int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ?
+                wctx->nb_item_type[wctx->level-1][section->id] :
+                wctx->nb_item[wctx->level-1];
+            av_bprintf(buf, ".%d", n);
+        }
+    }
+
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
+        writer_printf(wctx, "[%s]\n", buf->str);
+}
+
+static void ini_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    writer_printf(wctx, "%s=", ini_escape_str(&buf, key));
+    av_bprint_clear(&buf);
+    writer_printf(wctx, "%s\n", ini_escape_str(&buf, value));
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void ini_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+    writer_printf(wctx, "%s=%"PRId64"\n", key, value);
+}
+
+const AVTextFormatter avtextformatter_ini = {
+    .name                  = "ini",
+    .priv_size             = sizeof(INIContext),
+    .print_section_header  = ini_print_section_header,
+    .print_integer         = ini_print_int,
+    .print_string          = ini_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
+    .priv_class            = &ini_class,
+};
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
new file mode 100644
index 0000000000..4579c8a3d9
--- /dev/null
+++ b/fftools/textformat/tf_json.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_CLASS(name)                   \
+static const char *name##_get_name(void *ctx)       \
+{                                                   \
+    return #name ;                                  \
+}                                                   \
+static const AVClass name##_class = {               \
+    .class_name = #name,                            \
+    .item_name  = name##_get_name,                  \
+    .option     = name##_options                    \
+}
+
+
+/* JSON output */
+
+typedef struct JSONContext {
+    const AVClass *class;
+    int indent_level;
+    int compact;
+    const char *item_sep, *item_start_end;
+} JSONContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(JSONContext, x)
+
+static const AVOption json_options[]= {
+    { "compact", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { "c",       "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
+    { NULL }
+};
+
+DEFINE_FORMATTER_CLASS(json);
+
+static av_cold int json_init(AVTextFormatContext *wctx)
+{
+    JSONContext *json = wctx->priv;
+
+    json->item_sep       = json->compact ? ", " : ",\n";
+    json->item_start_end = json->compact ? " "  : "\n";
+
+    return 0;
+}
+
+static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx)
+{
+    static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0};
+    static const char json_subst[]  = {'"', '\\',  'b',  'f',  'n',  'r',  't', 0};
+    const char *p;
+
+    for (p = src; *p; p++) {
+        char *s = strchr(json_escape, *p);
+        if (s) {
+            av_bprint_chars(dst, '\\', 1);
+            av_bprint_chars(dst, json_subst[s - json_escape], 1);
+        } else if ((unsigned char)*p < 32) {
+            av_bprintf(dst, "\\u00%02x", *p & 0xff);
+        } else {
+            av_bprint_chars(dst, *p, 1);
+        }
+    }
+    return dst->str;
+}
+
+#define JSON_INDENT() writer_printf(wctx, "%*c", json->indent_level * 4, ' ')
+
+static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+    JSONContext *json = wctx->priv;
+    AVBPrint buf;
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const struct AVTextFormatSection *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    if (wctx->level && wctx->nb_item[wctx->level-1])
+        writer_put_str(wctx, ",\n");
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
+        writer_put_str(wctx, "{\n");
+        json->indent_level++;
+    } else {
+        av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+        json_escape_str(&buf, section->name, wctx);
+        JSON_INDENT();
+
+        json->indent_level++;
+        if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
+            writer_printf(wctx, "\"%s\": [\n", buf.str);
+        } else if (parent_section && !(parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) {
+            writer_printf(wctx, "\"%s\": {%s", buf.str, json->item_start_end);
+        } else {
+            writer_printf(wctx, "{%s", json->item_start_end);
+
+            /* this is required so the parser can distinguish between packets and frames */
+            if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE) {
+                if (!json->compact)
+                    JSON_INDENT();
+                writer_printf(wctx, "\"type\": \"%s\"", section->name);
+                wctx->nb_item[wctx->level]++;
+            }
+        }
+        av_bprint_finalize(&buf, NULL);
+    }
+}
+
+static void json_print_section_footer(AVTextFormatContext *wctx)
+{
+    JSONContext *json = wctx->priv;
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    if (wctx->level == 0) {
+        json->indent_level--;
+        writer_put_str(wctx, "\n}\n");
+    } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
+        writer_w8(wctx, '\n');
+        json->indent_level--;
+        JSON_INDENT();
+        writer_w8(wctx, ']');
+    } else {
+        writer_put_str(wctx, json->item_start_end);
+        json->indent_level--;
+        if (!json->compact)
+            JSON_INDENT();
+        writer_w8(wctx, '}');
+    }
+}
+
+static inline void json_print_item_str(AVTextFormatContext *wctx,
+                                       const char *key, const char *value)
+{
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    writer_printf(wctx, "\"%s\":", json_escape_str(&buf, key,   wctx));
+    av_bprint_clear(&buf);
+    writer_printf(wctx, " \"%s\"", json_escape_str(&buf, value, wctx));
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
+    JSONContext *json = wctx->priv;
+    const struct AVTextFormatSection *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE))
+        writer_put_str(wctx, json->item_sep);
+    if (!json->compact)
+        JSON_INDENT();
+    json_print_item_str(wctx, key, value);
+}
+
+static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+    JSONContext *json = wctx->priv;
+    const struct AVTextFormatSection *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+    AVBPrint buf;
+
+    if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE))
+        writer_put_str(wctx, json->item_sep);
+    if (!json->compact)
+        JSON_INDENT();
+
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+    writer_printf(wctx, "\"%s\": %"PRId64, json_escape_str(&buf, key, wctx), value);
+    av_bprint_finalize(&buf, NULL);
+}
+
+const AVTextFormatter avtextformatter_json = {
+    .name                 = "json",
+    .priv_size            = sizeof(JSONContext),
+    .init                 = json_init,
+    .print_section_header = json_print_section_header,
+    .print_section_footer = json_print_section_footer,
+    .print_integer        = json_print_int,
+    .print_string         = json_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
+    .priv_class           = &json_class,
+};
+
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
new file mode 100644
index 0000000000..04c43fb85d
--- /dev/null
+++ b/fftools/textformat/tf_xml.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextformat.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/error.h>
+#include <libavutil/macros.h>
+#include <libavutil/opt.h>
+
+#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
+#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
+#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
+
+#define DEFINE_FORMATTER_CLASS(name)                   \
+static const char *name##_get_name(void *ctx)       \
+{                                                   \
+    return #name ;                                  \
+}                                                   \
+static const AVClass name##_class = {               \
+    .class_name = #name,                            \
+    .item_name  = name##_get_name,                  \
+    .option     = name##_options                    \
+}
+
+/* XML output */
+
+typedef struct XMLContext {
+    const AVClass *class;
+    int within_tag;
+    int indent_level;
+    int fully_qualified;
+    int xsd_strict;
+} XMLContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(XMLContext, x)
+
+static const AVOption xml_options[] = {
+    {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0},  0, 1 },
+    {"q",               "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0},  0, 1 },
+    {"xsd_strict",      "ensure that the output is XSD compliant",         OFFSET(xsd_strict),      AV_OPT_TYPE_BOOL, {.i64=0},  0, 1 },
+    {"x",               "ensure that the output is XSD compliant",         OFFSET(xsd_strict),      AV_OPT_TYPE_BOOL, {.i64=0},  0, 1 },
+    {NULL},
+};
+
+DEFINE_FORMATTER_CLASS(xml);
+
+static av_cold int xml_init(AVTextFormatContext *wctx)
+{
+    XMLContext *xml = wctx->priv;
+
+    if (xml->xsd_strict) {
+        xml->fully_qualified = 1;
+#define CHECK_COMPLIANCE(opt, opt_name)                                 \
+        if (opt) {                                                      \
+            av_log(wctx, AV_LOG_ERROR,                                  \
+                   "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \
+                   "You need to disable such option with '-no%s'\n", opt_name, opt_name); \
+            return AVERROR(EINVAL);                                     \
+        }
+        ////CHECK_COMPLIANCE(show_private_data, "private");
+        CHECK_COMPLIANCE(wctx->show_value_unit,   "unit");
+        CHECK_COMPLIANCE(wctx->use_value_prefix,  "prefix");
+    }
+
+    return 0;
+}
+
+#define XML_INDENT() writer_printf(wctx, "%*c", xml->indent_level * 4, ' ')
+
+static void xml_print_section_header(AVTextFormatContext *wctx, const void *data)
+{
+    XMLContext *xml = wctx->priv;
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const struct AVTextFormatSection *parent_section = wctx->level ?
+        wctx->section[wctx->level-1] : NULL;
+
+    if (wctx->level == 0) {
+        const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+            "xmlns:ffprobe=\"http://www.ffmpeg.org/schema/ffprobe\" "
+            "xsi:schemaLocation=\"http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd\"";
+
+        writer_put_str(wctx, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+        writer_printf(wctx, "<%sffprobe%s>\n",
+               xml->fully_qualified ? "ffprobe:" : "",
+               xml->fully_qualified ? qual : "");
+        return;
+    }
+
+    if (xml->within_tag) {
+        xml->within_tag = 0;
+        writer_put_str(wctx, ">\n");
+    }
+
+    if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
+        wctx->level && wctx->nb_item[wctx->level-1])
+        writer_w8(wctx, '\n');
+    xml->indent_level++;
+
+    if (section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS)) {
+        XML_INDENT(); writer_printf(wctx, "<%s", section->name);
+
+        if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
+            AVBPrint buf;
+            av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+            av_bprint_escape(&buf, section->get_type(data), NULL,
+                             AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
+            writer_printf(wctx, " type=\"%s\"", buf.str);
+        }
+        writer_printf(wctx, ">\n", section->name);
+    } else {
+        XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
+        xml->within_tag = 1;
+    }
+}
+
+static void xml_print_section_footer(AVTextFormatContext *wctx)
+{
+    XMLContext *xml = wctx->priv;
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    if (wctx->level == 0) {
+        writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
+    } else if (xml->within_tag) {
+        xml->within_tag = 0;
+        writer_put_str(wctx, "/>\n");
+        xml->indent_level--;
+    } else {
+        XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
+        xml->indent_level--;
+    }
+}
+
+static void xml_print_value(AVTextFormatContext *wctx, const char *key,
+                            const char *str, int64_t num, const int is_int)
+{
+    AVBPrint buf;
+    XMLContext *xml = wctx->priv;
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS) {
+        xml->indent_level++;
+        XML_INDENT();
+        av_bprint_escape(&buf, key, NULL,
+                         AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
+        writer_printf(wctx, "<%s key=\"%s\"",
+                      section->element_name, buf.str);
+        av_bprint_clear(&buf);
+
+        if (is_int) {
+            writer_printf(wctx, " value=\"%"PRId64"\"/>\n", num);
+        } else {
+            av_bprint_escape(&buf, str, NULL,
+                             AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
+            writer_printf(wctx, " value=\"%s\"/>\n", buf.str);
+        }
+        xml->indent_level--;
+    } else {
+        if (wctx->nb_item[wctx->level])
+            writer_w8(wctx, ' ');
+
+        if (is_int) {
+            writer_printf(wctx, "%s=\"%"PRId64"\"", key, num);
+        } else {
+            av_bprint_escape(&buf, str, NULL,
+                             AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
+            writer_printf(wctx, "%s=\"%s\"", key, buf.str);
+        }
+    }
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
+    xml_print_value(wctx, key, value, 0, 0);
+}
+
+static void xml_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
+{
+    xml_print_value(wctx, key, NULL, value, 1);
+}
+
+const AVTextFormatter avtextformatter_xml = {
+    .name                 = "xml",
+    .priv_size            = sizeof(XMLContext),
+    .init                 = xml_init,
+    .print_section_header = xml_print_section_header,
+    .print_section_footer = xml_print_section_footer,
+    .print_integer        = xml_print_int,
+    .print_string         = xml_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
+    .priv_class           = &xml_class,
+};
+
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
new file mode 100644
index 0000000000..e1605a1272
--- /dev/null
+++ b/fftools/textformat/tw_avio.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+
+#include "avtextwriters.h"
+#include "libavutil/opt.h"
+
+/* AVIO Writer */
+
+# define WRITER_NAME "aviowriter"
+
+typedef struct IOWriterContext {
+    const AVClass *class;
+    AVIOContext *avio_context;
+    int close_on_uninit;
+} IOWriterContext;
+
+static const char *iowriter_get_name(void *ctx)
+{
+    return WRITER_NAME;
+}
+
+static const AVClass iowriter_class = {
+    .class_name = WRITER_NAME,
+    .item_name = iowriter_get_name,
+};
+
+static av_cold void iowriter_uninit(AVTextWriterContext *wctx)
+{
+    IOWriterContext *ctx = wctx->priv;
+
+    if (ctx->close_on_uninit && ctx->avio_context) {
+        avio_flush(ctx->avio_context);
+        avio_close(ctx->avio_context);
+    }
+}
+
+static void io_w8(AVTextWriterContext *wctx, int b)
+{
+    IOWriterContext *ctx = wctx->priv;
+    avio_w8(ctx->avio_context, b);
+}
+
+static void io_put_str(AVTextWriterContext *wctx, const char *str)
+{
+    IOWriterContext *ctx = wctx->priv;
+    avio_write(ctx->avio_context, str, strlen(str));
+}
+
+static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+{
+    IOWriterContext *ctx = wctx->priv;
+    va_list ap;
+
+    va_start(ap, fmt);
+    avio_vprintf(ctx->avio_context, fmt, ap);
+    va_end(ap);
+}
+
+
+const AVTextWriter avtextwriter_avio = {
+    .name                 = WRITER_NAME,
+    .priv_size            = sizeof(IOWriterContext),
+    .uninit               = iowriter_uninit,
+    .priv_class           = &iowriter_class,
+    .writer_put_str       = io_put_str,
+    .writer_printf        = io_printf,
+    .writer_w8            = io_w8
+};
+
+int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit)
+{
+    IOWriterContext *ctx;
+    int ret;
+
+
+    ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
+    if (ret < 0)
+        return ret;
+
+    ctx = (*pwctx)->priv;
+
+    if ((ret = avio_open(&ctx->avio_context, output_filename, AVIO_FLAG_WRITE)) < 0) {
+        av_log(ctx, AV_LOG_ERROR,
+               "Failed to open output '%s' with error: %s\n", output_filename, av_err2str(ret));
+        avtextwriter_context_close(pwctx);
+        return ret;
+    }
+
+    ctx->close_on_uninit = close_on_uninit;
+
+    return ret;
+}
+
+
+int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
+{
+    IOWriterContext *ctx;
+    int ret;
+
+    ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
+    if (ret < 0)
+        return ret;
+
+    ctx = (*pwctx)->priv;
+    ctx->avio_context = avio_ctx;
+    ctx->close_on_uninit = close_on_uninit;
+
+    return ret;
+}
diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
new file mode 100644
index 0000000000..4aeec4afa0
--- /dev/null
+++ b/fftools/textformat/tw_buffer.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdarg.h>
+
+#include "avtextwriters.h"
+#include "libavutil/opt.h"
+#include "libavutil/bprint.h"
+
+/* Buffer Writer */
+
+# define WRITER_NAME "bufferwriter"
+
+typedef struct BufferWriterContext {
+    const AVClass *class;
+    AVBPrint *buffer;
+} BufferWriterContext;
+
+static const char *bufferwriter_get_name(void *ctx)
+{
+    return WRITER_NAME;
+}
+
+static const AVClass bufferwriter_class = {
+    .class_name = WRITER_NAME,
+    .item_name = bufferwriter_get_name,
+};
+
+static void buffer_w8(AVTextWriterContext *wctx, int b)
+{
+    BufferWriterContext *ctx = wctx->priv;
+    av_bprintf(ctx->buffer, "%c", b);
+}
+
+static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
+{
+    BufferWriterContext *ctx = wctx->priv;
+    av_bprintf(ctx->buffer, "%s", str);
+}
+
+static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+{
+    BufferWriterContext *ctx = wctx->priv;
+
+    va_list vargs;
+    va_start(vargs, fmt);
+    av_vbprintf(ctx->buffer, fmt, vargs);
+    va_end(vargs);
+}
+
+
+const AVTextWriter avtextwriter_buffer = {
+    .name                 = WRITER_NAME,
+    .priv_size            = sizeof(BufferWriterContext),
+    .priv_class           = &bufferwriter_class,
+    .writer_put_str       = buffer_put_str,
+    .writer_printf        = buffer_printf,
+    .writer_w8            = buffer_w8
+};
+
+int avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer)
+{
+    BufferWriterContext *ctx;
+    int ret;
+
+    ret = avtextwriter_context_open(pwctx, &avtextwriter_buffer);
+    if (ret < 0)
+        return ret;
+
+    ctx = (*pwctx)->priv;
+    ctx->buffer = buffer;
+
+    return ret;
+}
diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
new file mode 100644
index 0000000000..b9a613ad61
--- /dev/null
+++ b/fftools/textformat/tw_stdout.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) The ffmpeg developers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avtextwriters.h"
+#include "libavutil/opt.h"
+
+/* STDOUT Writer */
+
+# define WRITER_NAME "stdoutwriter"
+
+typedef struct StdOutWriterContext {
+    const AVClass *class;
+} StdOutWriterContext;
+
+static const char *stdoutwriter_get_name(void *ctx)
+{
+    return WRITER_NAME;
+}
+
+static const AVClass stdoutwriter_class = {
+    .class_name = WRITER_NAME,
+    .item_name = stdoutwriter_get_name,
+};
+
+static inline void stdout_w8(AVTextWriterContext *wctx, int b)
+{
+    printf("%c", b);
+}
+
+static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
+{
+    printf("%s", str);
+}
+
+static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    va_end(ap);
+}
+
+
+static const AVTextWriter avtextwriter_stdout = {
+    .name                 = WRITER_NAME,
+    .priv_size            = sizeof(StdOutWriterContext),
+    .priv_class           = &stdoutwriter_class,
+    .writer_put_str       = stdout_put_str,
+    .writer_printf        = stdout_printf,
+    .writer_w8            = stdout_w8
+};
+
+int avtextwriter_create_stdout(AVTextWriterContext **pwctx)
+{
+    int ret;
+
+    ret = avtextwriter_context_open(pwctx, &avtextwriter_stdout);
+
+    return ret;
+}
-- 
ffmpeg-codebot

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

  reply	other threads:[~2025-03-01 10:02 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-02-19  9:59 [FFmpeg-devel] [PATCH 0/3] print_graphs: Complete Filtergraph Printing ffmpegagent
2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 1/3] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 2/3] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
2025-02-21  9:22   ` Andreas Rheinhardt
2025-02-21  9:42     ` Soft Works
2025-02-21 11:11       ` Andreas Rheinhardt
2025-02-21 11:25         ` Soft Works
2025-02-21 13:09   ` Nicolas George
2025-02-21 13:49     ` Soft Works
2025-02-24 10:41       ` Nicolas George
2025-02-24 13:19         ` Soft Works
2025-02-26 14:42           ` Nicolas George
2025-02-27 13:11             ` Soft Works
2025-02-19  9:59 ` [FFmpeg-devel] [PATCH 3/3] fftools: Enable filtergraph printing and update docs softworkz
2025-02-21 11:27 ` [FFmpeg-devel] [PATCH v2 0/4] print_graphs: Complete Filtergraph Printing ffmpegagent
2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 1/4] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 2/4] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() softworkz
2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 3/4] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
2025-02-21 11:27   ` [FFmpeg-devel] [PATCH v2 4/4] fftools: Enable filtergraph printing and update docs softworkz
2025-03-01 10:01   ` [FFmpeg-devel] [PATCH v3 0/7] print_graphs: Complete Filtergraph Printing ffmpegagent
2025-03-01 10:01     ` softworkz [this message]
2025-03-01 10:01     ` [FFmpeg-devel] [PATCH v3 2/7] fftools/ffprobe: Change to use textformat api softworkz
2025-03-01 10:02     ` [FFmpeg-devel] [PATCH v3 3/7] fftools/ffprobe: Rename writer_print_section_* and WriterContext softworkz
2025-03-01 10:02     ` [FFmpeg-devel] [PATCH v3 4/7] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-03-01 10:02     ` [FFmpeg-devel] [PATCH v3 5/7] avfilter/avfilter Add avfilter_link_get_hw_frames_ctx() softworkz
2025-03-01 10:02     ` [FFmpeg-devel] [PATCH v3 6/7] fftools/ffmpeg_graphprint: Add options for filtergraph printing softworkz
2025-03-01 10:02     ` [FFmpeg-devel] [PATCH v3 7/7] fftools: Enable filtergraph printing and update docs softworkz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=6239813ba0e293bd427cecf8437aadef778ea3ee.1740823324.git.ffmpegagent@gmail.com \
    --to=ffmpegagent@gmail.com \
    --cc=andreas.rheinhardt@outlook.com \
    --cc=ffmpeg-devel@ffmpeg.org \
    --cc=softworkz-at-hotmail.com@ffmpeg.org \
    --cc=softworkz@hotmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git