Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing
@ 2025-04-14 12:46 ffmpegagent
  2025-04-14 12:46 ` [FFmpeg-devel] [PATCH 1/9] fftools/textformat: Formatting and whitespace changes softworkz
                   ` (9 more replies)
  0 siblings, 10 replies; 130+ messages in thread
From: ffmpegagent @ 2025-04-14 12:46 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

Shortest cover letter for my longest-running FFmpeg patchset:

 * Apply
 * Build
 * Add the "-sg" switch to any FFmpeg command line

SG = Show Graph

Documentation and examples can be found here:

https://github.com/softworkz/ffmpeg_output_apis/wiki

softworkz (9):
  fftools/textformat: Formatting and whitespace changes
  fftools/textformat: Quality improvements
  fftools/textformat: Introduce common header and deduplicate code
  fftools/textformat: Add function avtext_print_integer_flags()
  fftools/ffmpeg_filter: Move some declaration to new header file
  avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  fftools/resources: Add resource manager files
  fftools/graphprint: Add execution graph printing
  fftools/graphprint: Now, make it a Killer-Feature!

 doc/APIchanges                     |    3 +
 doc/ffmpeg.texi                    |   14 +
 ffbuild/common.mak                 |   28 +-
 fftools/Makefile                   |   18 +
 fftools/ffmpeg.c                   |    4 +
 fftools/ffmpeg.h                   |    4 +
 fftools/ffmpeg_filter.c            |  195 +----
 fftools/ffmpeg_filter.h            |  234 ++++++
 fftools/ffmpeg_opt.c               |   17 +
 fftools/graph/filelauncher.c       |  204 +++++
 fftools/graph/graphprint.c         | 1146 ++++++++++++++++++++++++++++
 fftools/graph/graphprint.h         |   62 ++
 fftools/resources/.gitignore       |    4 +
 fftools/resources/Makefile         |   27 +
 fftools/resources/graph.css        |  353 +++++++++
 fftools/resources/graph.html       |   86 +++
 fftools/resources/resman.c         |  213 ++++++
 fftools/resources/resman.h         |   50 ++
 fftools/textformat/avtextformat.c  |  248 +++---
 fftools/textformat/avtextformat.h  |   53 +-
 fftools/textformat/avtextwriters.h |   11 +-
 fftools/textformat/tf_compact.c    |  120 +--
 fftools/textformat/tf_default.c    |   55 +-
 fftools/textformat/tf_flat.c       |   53 +-
 fftools/textformat/tf_ini.c        |   62 +-
 fftools/textformat/tf_internal.h   |   85 +++
 fftools/textformat/tf_json.c       |   56 +-
 fftools/textformat/tf_mermaid.c    |  655 ++++++++++++++++
 fftools/textformat/tf_mermaid.h    |   41 +
 fftools/textformat/tf_xml.c        |   68 +-
 fftools/textformat/tw_avio.c       |   16 +-
 fftools/textformat/tw_buffer.c     |    7 +-
 fftools/textformat/tw_stdout.c     |    8 +-
 libavfilter/avfilter.c             |    9 +
 libavfilter/avfilter.h             |   12 +
 35 files changed, 3675 insertions(+), 546 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h
 create mode 100644 fftools/graph/filelauncher.c
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h
 create mode 100644 fftools/textformat/tf_internal.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h


base-commit: 3b2a9410ef091cd2eee9f652e98aae47ec9f3112
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-66%2Fsoftworkz%2Fsubmit_print_execution_graph-v1
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-66/softworkz/submit_print_execution_graph-v1
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/66
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH 1/9] fftools/textformat: Formatting and whitespace changes
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
@ 2025-04-14 12:46 ` softworkz
  2025-04-21 16:52   ` Stefano Sabatini
  2025-04-14 12:46 ` [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements softworkz
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-14 12:46 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c  | 104 +++++++++++++++--------------
 fftools/textformat/avtextformat.h  |  16 ++---
 fftools/textformat/avtextwriters.h |  11 ++-
 fftools/textformat/tf_compact.c    |  91 ++++++++++++++-----------
 fftools/textformat/tf_default.c    |  20 +++---
 fftools/textformat/tf_flat.c       |  26 ++++----
 fftools/textformat/tf_ini.c        |  36 +++++-----
 fftools/textformat/tf_json.c       |  10 +--
 fftools/textformat/tf_xml.c        |  30 +++++----
 9 files changed, 183 insertions(+), 161 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 6c09f9d2cd..1ce51d11e2 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -34,9 +34,9 @@
 #include "libavutil/opt.h"
 #include "avtextformat.h"
 
-#define SECTION_ID_NONE -1
+#define SECTION_ID_NONE (-1)
 
-#define SHOW_OPTIONAL_FIELDS_AUTO       -1
+#define SHOW_OPTIONAL_FIELDS_AUTO      (-1)
 #define SHOW_OPTIONAL_FIELDS_NEVER       0
 #define SHOW_OPTIONAL_FIELDS_ALWAYS      1
 
@@ -64,14 +64,14 @@ static const char *textcontext_get_formatter_name(void *p)
 
 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" },
+      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"}},
+      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 }
 };
 
@@ -125,14 +125,18 @@ int avtext_context_close(AVTextFormatContext **ptctx)
 }
 
 
-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)
+int avtext_context_open(AVTextFormatContext      **ptctx,
+                        const AVTextFormatter     *formatter,
+                        AVTextWriterContext       *writer_context,
+                        const char                *args,
+                        const 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;
@@ -197,7 +201,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash) {
+    if (show_data_hash)
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -208,7 +212,6 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             return ret;
         }
-    }
 
     /* validate replace string */
     {
@@ -221,7 +224,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             if (ret < 0) {
                 AVBPrint bp;
                 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
-                bprint_bytes(&bp, p0, p-p0),
+                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);
@@ -248,15 +251,13 @@ fail:
 }
 
 /* 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_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)
+void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
@@ -272,8 +273,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx,
 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;
+    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]++;
@@ -285,8 +287,7 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
     tctx->level--;
 }
 
-void avtext_print_integer(AVTextFormatContext *tctx,
-                                        const char *key, int64_t val)
+void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
     const struct AVTextFormatSection *section = tctx->section[tctx->level];
 
@@ -324,11 +325,9 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
             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);
+                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);
@@ -340,11 +339,10 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
             av_bprint_append_data(&dstbuf, p0, p-p0);
     }
 
-    if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
+    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);
@@ -352,7 +350,11 @@ end:
 }
 
 struct unit_value {
-    union { double d; int64_t i; } val;
+    union {
+        double  d;
+        int64_t i;
+    } val;
+
     const char *unit;
 };
 
@@ -402,8 +404,9 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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 : "");
+                    prefix_string, tctx->show_value_unit ? uv.unit : "");
     }
 
     return buf;
@@ -427,8 +430,8 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 
     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)))
+            && (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)) {
@@ -440,11 +443,10 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
             if (ret < 0) goto end;
             tctx->formatter->print_string(tctx, key1, val1);
         end:
-            if (ret < 0) {
+            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 {
@@ -457,8 +459,7 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
     return ret;
 }
 
-void avtext_print_rational(AVTextFormatContext *tctx,
-                                         const char *key, AVRational q, char sep)
+void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep)
 {
     AVBPrint buf;
     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
@@ -467,7 +468,7 @@ void avtext_print_rational(AVTextFormatContext *tctx,
 }
 
 void avtext_print_time(AVTextFormatContext *tctx, const char *key,
-                              int64_t ts, const AVRational *time_base, int is_duration)
+                       int64_t ts, const AVRational *time_base, int is_duration)
 {
     char buf[128];
 
@@ -485,15 +486,14 @@ void avtext_print_time(AVTextFormatContext *tctx, const char *key,
 
 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)) {
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0))
         avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
-    } else {
+    else
         avtext_print_integer(tctx, key, ts);
-    }
 }
 
 void avtext_print_data(AVTextFormatContext *tctx, const char *name,
-                              const uint8_t *data, int size)
+                       const uint8_t *data, int size)
 {
     AVBPrint bp;
     int offset = 0, l, i;
@@ -521,12 +521,13 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 }
 
 void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
-                                   const uint8_t *data, int size)
+                            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));
@@ -552,7 +553,7 @@ void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
             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 --;
+            size--;
         }
         av_bprintf(&bp, "\n");
         offset += offset_add;
@@ -642,7 +643,8 @@ fail:
     return ret;
 }
 
-static const AVTextFormatter *registered_formatters[7+1];
+static const AVTextFormatter *registered_formatters[10 + 1];
+
 static void formatters_register_all(void)
 {
     static int initialized;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index 9fad3caae5..03564d14a7 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -86,17 +86,17 @@ typedef struct AVTextFormatter {
 #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
+    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
+    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
+    const AVTextFormatSection *sections; ///< array containing all sections
+    int nb_sections;                   ///< number of sections
 
-    int level;                      ///< current level, starting from 0
+    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];
diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index a62f2c8906..9438d21802 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -37,11 +37,11 @@ typedef struct AVTextWriter {
     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, ...);
+    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 {
@@ -49,7 +49,6 @@ typedef struct AVTextWriterContext {
     const AVTextWriter *writer;
     const char *name;
     void *priv;                     ///< private data for use by the writer
-
 } AVTextWriterContext;
 
 
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index 825b67bc6e..298b840739 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -61,10 +61,10 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 
     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 '\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)
@@ -81,6 +81,7 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 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)
@@ -117,16 +118,16 @@ typedef struct 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},
+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);
@@ -142,10 +143,13 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
     }
     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 {
+    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);
     }
@@ -165,8 +169,8 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
     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))))) {
+            (!(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 */
@@ -174,10 +178,10 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
         AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
 
         compact->nested_section[wctx->level] = 1;
-        compact->has_nested_elems[wctx->level-1] = 1;
+        compact->has_nested_elems[wctx->level - 1] = 1;
 
         av_bprintf(section_pbuf, "%s%s",
-                   wctx->section_pbuf[wctx->level-1].str, element_name);
+                   wctx->section_pbuf[wctx->level - 1].str, element_name);
 
         if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
             // add /TYPE to prefix
@@ -188,30 +192,33 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
                 char c =
                     (*p >= '0' && *p <= '9') ||
                     (*p >= 'a' && *p <= 'z') ||
-                    (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
+                    (*p >= 'A' && *p <= 'Z')
+                    ? (char)(char)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];
+        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])
+        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)))
+            !(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)
 {
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     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)))
+        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
         writer_w8(wctx, '\n');
 }
 
@@ -220,9 +227,12 @@ static void compact_print_str(AVTextFormatContext *wctx, const char *key, const
     CompactContext *compact = wctx->priv;
     AVBPrint buf;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
@@ -232,9 +242,12 @@ static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_
 {
     CompactContext *compact = wctx->priv;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
 }
 
@@ -256,15 +269,15 @@ const AVTextFormatter avtextformatter_compact = {
 #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},
+    { "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);
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 23575024c1..14ef9fe8f9 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -58,11 +58,11 @@ typedef struct DefaultContext {
 #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},
+    { "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);
@@ -71,7 +71,7 @@ DEFINE_FORMATTER_CLASS(default);
 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++)
+    for (i = 0; src[i] && i < dst_size - 1; i++)
         dst[i] = av_toupper(src[i]);
     dst[i] = 0;
     return dst;
@@ -87,10 +87,10 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 
     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))) {
+        !(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,
+                   wctx->section_pbuf[wctx->level - 1].str,
                    upcase_string(buf, sizeof(buf),
                                  av_x_if_null(section->element_name, section->name)));
     }
@@ -98,7 +98,7 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
     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)))
+    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));
 }
 
@@ -111,7 +111,7 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     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)))
+    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));
 }
 
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index 6971593c77..41a6b1c812 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -60,12 +60,12 @@ typedef struct 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},
+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);
@@ -129,16 +129,18 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
     av_bprint_clear(buf);
     if (!parent_section)
         return;
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+
+    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))) {
+        !(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];
+            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);
         }
     }
@@ -169,6 +171,6 @@ const AVTextFormatter avtextformatter_flat = {
     .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,
+    .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
index 1f4216069f..9e1aa60e09 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -65,9 +65,9 @@ typedef struct INIContext {
 #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},
+    { "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);
@@ -75,9 +75,9 @@ DEFINE_FORMATTER_CLASS(ini);
 static char *ini_escape_str(AVBPrint *dst, const char *src)
 {
     int i = 0;
-    char c = 0;
+    char c;
 
-    while (c = src[i++]) {
+    while ((c = src[i++])) {
         switch (c) {
         case '\b': av_bprintf(dst, "%s", "\\b"); break;
         case '\f': av_bprintf(dst, "%s", "\\f"); break;
@@ -85,9 +85,11 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
         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);
+        case '#':
+        case '=':
+        case ':':
+            av_bprint_chars(dst, '\\', 1);
+            /* fallthrough */
         default:
             if ((unsigned char)c < 32)
                 av_bprintf(dst, "\\x00%02x", c & 0xff);
@@ -113,23 +115,23 @@ static void ini_print_section_header(AVTextFormatContext *wctx, const void *data
         return;
     }
 
-    if (wctx->nb_item[wctx->level-1])
+    if (wctx->nb_item[wctx->level - 1])
         writer_w8(wctx, '\n');
 
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+    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))) {
+        !(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);
+            unsigned 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, ".%u", n);
         }
     }
 
-    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
         writer_printf(wctx, "[%s]\n", buf->str);
 }
 
@@ -155,6 +157,6 @@ const AVTextFormatter avtextformatter_ini = {
     .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,
+    .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
index de27a36e7e..24838b35ec 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -58,9 +58,9 @@ typedef struct 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 },
+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 }
 };
 
@@ -78,8 +78,8 @@ static av_cold int json_init(AVTextFormatContext *wctx)
 
 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};
+    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++) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index 57171c4cb3..76271dbaa6 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -61,11 +61,11 @@ typedef struct XMLContext {
 #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},
+    { "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);
@@ -107,8 +107,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 
         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 : "");
+                      xml->fully_qualified ? "ffprobe:" : "",
+                      xml->fully_qualified ? qual : "");
         return;
     }
 
@@ -118,12 +118,13 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
     }
 
     if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
-        wctx->level && wctx->nb_item[wctx->level-1])
+        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_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;
@@ -134,7 +135,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
         }
         writer_printf(wctx, ">\n", section->name);
     } else {
-        XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "<%s ", section->name);
         xml->within_tag = 1;
     }
 }
@@ -151,7 +153,8 @@ static void xml_print_section_footer(AVTextFormatContext *wctx)
         writer_put_str(wctx, "/>\n");
         xml->indent_level--;
     } else {
-        XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "</%s>\n", section->name);
         xml->indent_level--;
     }
 }
@@ -198,7 +201,8 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
     av_bprint_finalize(&buf, NULL);
 }
 
-static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
+static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
     xml_print_value(wctx, key, value, 0, 0);
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
  2025-04-14 12:46 ` [FFmpeg-devel] [PATCH 1/9] fftools/textformat: Formatting and whitespace changes softworkz
@ 2025-04-14 12:46 ` softworkz
  2025-04-15  1:05   ` Andreas Rheinhardt
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 3/9] fftools/textformat: Introduce common header and deduplicate code softworkz
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-14 12:46 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 121 +++++++++++++++++++-----------
 fftools/textformat/avtextformat.h |   6 +-
 fftools/textformat/tf_default.c   |   8 +-
 fftools/textformat/tf_ini.c       |   2 +-
 fftools/textformat/tf_json.c      |   8 +-
 fftools/textformat/tf_xml.c       |   3 -
 fftools/textformat/tw_avio.c      |   9 ++-
 7 files changed, 101 insertions(+), 56 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 1ce51d11e2..406025d19d 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
 
 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++)
+    for (unsigned i = 0; i < ubuf_size; i++)
         av_bprintf(bp, "%02X", ubuf[i]);
 }
 
@@ -110,8 +109,6 @@ int avtext_context_close(AVTextFormatContext **ptctx)
 
     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++)
@@ -141,12 +138,18 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
     AVTextFormatContext *tctx;
     int i, ret = 0;
 
-    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
+    if (!ptctx || !formatter)
+        return AVERROR(EINVAL);
+
+    if (!formatter->priv_size && formatter->priv_class)
+        return AVERROR(EINVAL);
+
+    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
-    if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
+    if (formatter->priv_size && !((tctx->priv = av_mallocz(formatter->priv_size)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
@@ -215,15 +218,15 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
 
     /* validate replace string */
     {
-        const uint8_t *p = tctx->string_validation_replacement;
-        const uint8_t *endp = p + strlen(p);
+        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
+        const uint8_t *endp = p + strlen((const char *)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);
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
                 bprint_bytes(&bp, p0, p - p0),
                     av_log(tctx, AV_LOG_ERROR,
                            "Invalid UTF8 sequence %s found in string validation replace '%s'\n",
@@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
 
 void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
+    if (!tctx || section_id < 0 || section_id >= tctx->nb_sections)
+        return;
+
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
 
@@ -272,6 +278,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
 
 void avtext_print_section_footer(AVTextFormatContext *tctx)
 {
+    if (!tctx || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
     int section_id = tctx->section[tctx->level]->id;
     int parent_section_id = tctx->level
         ? tctx->section[tctx->level - 1]->id
@@ -289,7 +298,12 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
+
+    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
+    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);
@@ -299,24 +313,28 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
 
 static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
 {
-    const uint8_t *p, *endp;
+    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
     AVBPrint dstbuf;
+    AVBPrint bp;
     int invalid_chars_nb = 0, ret = 0;
 
+    if (!tctx || !dstp || !src)
+        return AVERROR(EINVAL);
+
+    *dstp = NULL;
     av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
 
-    endp = src + strlen(src);
-    for (p = src; *p;) {
-        uint32_t code;
+    endp = srcp + strlen(src);
+    for (p = srcp; *p;) {
+        int32_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);
+            av_bprint_clear(&bp);
+            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;
         }
 
@@ -336,7 +354,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
         }
 
         if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
-            av_bprint_append_data(&dstbuf, p0, p-p0);
+            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
     }
 
     if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
@@ -346,6 +364,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
 end:
     av_bprint_finalize(&dstbuf, dstp);
+    av_bprint_finalize(&bp, NULL);
     return ret;
 }
 
@@ -358,17 +377,18 @@ struct unit_value {
     const char *unit;
 };
 
-static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
+static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
 {
     double vald;
-    int64_t vali;
+    int64_t vali = 0;
     int show_float = 0;
 
     if (uv.unit == unit_second_str) {
         vald = uv.val.d;
         show_float = 1;
     } else {
-        vald = vali = uv.val.i;
+        vald = (double)uv.val.i;
+        vali = uv.val.i;
     }
 
     if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
@@ -387,17 +407,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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);
+                index = (int64_t)(log2(vald) / 10);
+                index = av_clip64(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);
+                index = (int64_t)(log10(vald) / 3);
+                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
                 vald /= si_prefixes[index].dec_val;
                 prefix_string = si_prefixes[index].dec_str;
             }
-            vali = vald;
+            vali = (int64_t)vald;
         }
 
         if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
@@ -425,9 +445,14 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
 
 int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
     int ret = 0;
 
+    if (!tctx || !key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return AVERROR(EINVAL);
+
+    section = tctx->section[tctx->level];
+
     if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
         (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
             && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
@@ -462,7 +487,7 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 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_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
     avtext_print_string(tctx, key, buf.str, 0);
 }
@@ -470,12 +495,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
 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);
+        char buf[128];
+        double d = av_q2d(*time_base) * (double)ts;
         struct unit_value uv;
         uv.val.d = d;
         uv.unit = unit_second_str;
@@ -496,7 +520,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
                        const uint8_t *data, int size)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -523,25 +548,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 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 };
+    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
+    int len;
 
     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);
+    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
+    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
     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)
+                           uint8_t *data, int size, const char *format,
+                           int columns, int bytes, int offset_add)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
+
+    if (!name || !data || !format || columns <= 0 || bytes <= 0)
+        return;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -607,12 +636,18 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
     AVTextWriterContext *wctx;
     int ret = 0;
 
-    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
+    if (!pwctx || !writer)
+        return AVERROR(EINVAL);
+
+    if (!writer->priv_size && writer->priv_class)
+        return AVERROR(EINVAL);
+
+    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
-    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
+    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index 03564d14a7..e519094f4f 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -21,9 +21,7 @@
 #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"
@@ -103,7 +101,7 @@ struct AVTextFormatContext {
     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];
+    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
     AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
                                                   ///  used by various formatters
 
@@ -124,7 +122,7 @@ struct AVTextFormatContext {
 #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 14ef9fe8f9..3b05d25f36 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -70,9 +70,10 @@ 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;
+    unsigned i;
+
     for (i = 0; src[i] && i < dst_size - 1; i++)
-        dst[i] = av_toupper(src[i]);
+        dst[i] = (char)av_toupper(src[i]);
     dst[i] = 0;
     return dst;
 }
@@ -108,6 +109,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     const struct AVTextFormatSection *section = wctx->section[wctx->level];
     char buf[32];
 
+    if (!section)
+        return;
+
     if (def->noprint_wrappers || def->nested_section[wctx->level])
         return;
 
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index 9e1aa60e09..ec471fd480 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -92,7 +92,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
             /* fallthrough */
         default:
             if ((unsigned char)c < 32)
-                av_bprintf(dst, "\\x00%02x", c & 0xff);
+                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
             else
                 av_bprint_chars(dst, c, 1);
             break;
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index 24838b35ec..f286838d3c 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -82,13 +82,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
     static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
     const char *p;
 
+    if (!src) {
+        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source string\n");
+        return NULL;
+    }
+
     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);
+            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
         } else {
             av_bprint_chars(dst, *p, 1);
         }
@@ -107,6 +112,7 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
         wctx->section[wctx->level-1] : NULL;
 
     if (wctx->level && wctx->nb_item[wctx->level-1])
+    if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
     if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index 76271dbaa6..eceeda81e5 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -18,10 +18,7 @@
  * 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"
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
index d335d35a56..3c7492aa06 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -63,7 +63,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
 static void io_put_str(AVTextWriterContext *wctx, const char *str)
 {
     IOWriterContext *ctx = wctx->priv;
-    avio_write(ctx->avio_context, str, strlen(str));
+    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
 static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
@@ -89,10 +89,12 @@ const AVTextWriter avtextwriter_avio = {
 
 int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit)
 {
+    if (!pwctx || !output_filename || !output_filename[0])
+        return AVERROR(EINVAL);
+
     IOWriterContext *ctx;
     int ret;
 
-
     ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
     if (ret < 0)
         return ret;
@@ -114,6 +116,9 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
 
 int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
 {
+    if (!pwctx || !avio_ctx)
+        return AVERROR(EINVAL);
+
     IOWriterContext *ctx;
     int 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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH 3/9] fftools/textformat: Introduce common header and deduplicate code
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
  2025-04-14 12:46 ` [FFmpeg-devel] [PATCH 1/9] fftools/textformat: Formatting and whitespace changes softworkz
  2025-04-14 12:46 ` [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements softworkz
@ 2025-04-14 12:47 ` softworkz
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 4/9] fftools/textformat: Add function avtext_print_integer_flags() softworkz
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-14 12:47 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextwriters.h |  2 +-
 fftools/textformat/tf_compact.c    | 31 ++++-------
 fftools/textformat/tf_default.c    | 27 +++-------
 fftools/textformat/tf_flat.c       | 27 +++-------
 fftools/textformat/tf_ini.c        | 24 +++------
 fftools/textformat/tf_internal.h   | 85 ++++++++++++++++++++++++++++++
 fftools/textformat/tf_json.c       | 38 +++++--------
 fftools/textformat/tf_xml.c        | 35 +++++-------
 fftools/textformat/tw_avio.c       |  7 +--
 fftools/textformat/tw_buffer.c     |  7 +--
 fftools/textformat/tw_stdout.c     |  8 +--
 11 files changed, 150 insertions(+), 141 deletions(-)
 create mode 100644 fftools/textformat/tf_internal.h

diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index 9438d21802..0c2ed21a6f 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -41,7 +41,7 @@ typedef struct AVTextWriter {
     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, ...);
+    void (*writer_printf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
 } AVTextWriter;
 
 typedef struct AVTextWriterContext {
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index 298b840739..52bc8ad927 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include "avtextformat.h"
+#include "tf_internal.h"
 #include <libavutil/mem.h>
 #include <libavutil/avassert.h>
 #include <libavutil/bprint.h>
@@ -33,22 +34,6 @@
 #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 */
 
@@ -160,9 +145,12 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
+
     compact->terminate_line[wctx->level] = 1;
     compact->has_nested_elems[wctx->level] = 0;
 
@@ -213,8 +201,11 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
 
 static void compact_print_section_footer(AVTextFormatContext *wctx)
 {
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     CompactContext *compact = wctx->priv;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (!compact->nested_section[wctx->level] &&
         compact->terminate_line[wctx->level] &&
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 3b05d25f36..a9b247c769 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -25,26 +25,12 @@
 #include <string.h>
 
 #include "avtextformat.h"
+#include "tf_internal.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 {
@@ -82,9 +68,11 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(&wctx->section_pbuf[wctx->level]);
     if (parent_section &&
@@ -106,7 +94,8 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 static void default_print_section_footer(AVTextFormatContext *wctx)
 {
     DefaultContext *def = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
     char buf[32];
 
     if (!section)
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index 41a6b1c812..c0a8d8c437 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include "avtextformat.h"
+#include "tf_internal.h"
 #include <libavutil/mem.h>
 #include <libavutil/avassert.h>
 #include <libavutil/bprint.h>
@@ -32,23 +33,7 @@
 #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 */
+ /* Flat output */
 
 typedef struct FlatContext {
     const AVClass *class;
@@ -121,9 +106,11 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     /* build section header */
     av_bprint_clear(buf);
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index ec471fd480..e9a11fde99 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -25,26 +25,12 @@
 #include <string.h>
 
 #include "avtextformat.h"
+#include "tf_internal.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 {
@@ -105,9 +91,11 @@ 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(buf);
     if (!parent_section) {
diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
new file mode 100644
index 0000000000..7b326328cb
--- /dev/null
+++ b/fftools/textformat/tf_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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
+ */
+
+/**
+ * @file
+ * Internal utilities for text formatters.
+ */
+
+#ifndef FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+#define FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+
+#include "avtextformat.h"
+
+#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                    \
+}
+
+
+/**
+ * Safely validate and access a section at a given level
+ */
+static inline const AVTextFormatSection *tf_get_section(AVTextFormatContext *tfc, int level)
+{
+    if (!tfc || level < 0 || level >= SECTION_MAX_NB_LEVELS || !tfc->section[level]) {
+        if (tfc)
+            av_log(tfc, AV_LOG_ERROR, "Invalid section access at level %d\n", level);
+        return NULL;
+    }
+    return tfc->section[level];
+}
+
+/**
+ * Safely access the parent section
+ */
+static inline const AVTextFormatSection *tf_get_parent_section(AVTextFormatContext *tfc, int level)
+{
+    if (level <= 0)
+        return NULL;
+
+    return tf_get_section(tfc, level - 1);
+}
+
+static inline void writer_w8(AVTextFormatContext *wctx, int b)
+{
+    wctx->writer->writer->writer_w8(wctx->writer, b);
+}
+
+static inline void writer_put_str(AVTextFormatContext *wctx, const char *str)
+{
+    wctx->writer->writer->writer_put_str(wctx->writer, str);
+}
+
+static inline void writer_printf(AVTextFormatContext *wctx, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    wctx->writer->writer->writer_printf(wctx->writer, fmt, args);
+    va_end(args);
+}
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_INTERNAL_H */
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index f286838d3c..a8f23a989f 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -25,27 +25,12 @@
 #include <string.h>
 
 #include "avtextformat.h"
+#include "tf_internal.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 {
@@ -105,13 +90,14 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
 
 static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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])
+    if (!section)
+        return;
+
     if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
@@ -145,8 +131,11 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 
 static void json_print_section_footer(AVTextFormatContext *wctx)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         json->indent_level--;
@@ -179,9 +168,8 @@ static inline void json_print_item_str(AVTextFormatContext *wctx,
 
 static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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);
@@ -192,9 +180,8 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 
 static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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))
@@ -218,4 +205,3 @@ const AVTextFormatter avtextformatter_json = {
     .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
index eceeda81e5..fd3d73888e 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -22,6 +22,7 @@
 #include <string.h>
 
 #include "avtextformat.h"
+#include "tf_internal.h"
 #include <libavutil/mem.h>
 #include <libavutil/avassert.h>
 #include <libavutil/bprint.h>
@@ -29,21 +30,6 @@
 #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 {
@@ -93,9 +79,11 @@ static av_cold int xml_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
@@ -141,7 +129,10 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 static void xml_print_section_footer(AVTextFormatContext *wctx)
 {
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
@@ -161,7 +152,10 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
 {
     AVBPrint buf;
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
 
@@ -219,4 +213,3 @@ const AVTextFormatter avtextformatter_xml = {
     .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
index 3c7492aa06..f9f9cc6d5c 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -66,14 +66,11 @@ static void io_put_str(AVTextWriterContext *wctx, const char *str)
     avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
-static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void io_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     IOWriterContext *ctx = wctx->priv;
-    va_list ap;
 
-    va_start(ap, fmt);
-    avio_vprintf(ctx->avio_context, fmt, ap);
-    va_end(ap);
+    avio_vprintf(ctx->avio_context, fmt, vl);
 }
 
 
diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
index f8b38414a6..f861722247 100644
--- a/fftools/textformat/tw_buffer.c
+++ b/fftools/textformat/tw_buffer.c
@@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
     av_bprintf(ctx->buffer, "%s", str);
 }
 
-static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     BufferWriterContext *ctx = wctx->priv;
 
-    va_list vargs;
-    va_start(vargs, fmt);
-    av_vbprintf(ctx->buffer, fmt, vargs);
-    va_end(vargs);
+    av_vbprintf(ctx->buffer, fmt, vl);
 }
 
 
diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
index 23de6f671f..dace55f38a 100644
--- a/fftools/textformat/tw_stdout.c
+++ b/fftools/textformat/tw_stdout.c
@@ -53,13 +53,9 @@ static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
     printf("%s", str);
 }
 
-static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
-    va_list ap;
-
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
+    vprintf(fmt, vl);
 }
 
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH 4/9] fftools/textformat: Add function avtext_print_integer_flags()
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
                   ` (2 preceding siblings ...)
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 3/9] fftools/textformat: Introduce common header and deduplicate code softworkz
@ 2025-04-14 12:47 ` softworkz
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 5/9] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-14 12:47 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This function works analog to the avtext_print_string() which already
has a flags parameter.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 21 +++++++++++++++++++++
 fftools/textformat/avtextformat.h |  2 ++
 2 files changed, 23 insertions(+)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 406025d19d..b862b70d9f 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -311,6 +311,27 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
     }
 }
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags)
+{
+    const AVTextFormatSection *section;
+
+    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
+    section = tctx->section[tctx->level];
+
+    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;
+
+    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, *srcp = (const uint8_t *)src;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index e519094f4f..391ecdb624 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -139,6 +139,8 @@ void avtext_print_section_footer(AVTextFormatContext *tctx);
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags);
+
 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);
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH 5/9] fftools/ffmpeg_filter: Move some declaration to new header file
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
                   ` (3 preceding siblings ...)
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 4/9] fftools/textformat: Add function avtext_print_integer_flags() softworkz
@ 2025-04-14 12:47 ` softworkz
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 6/9] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-14 12:47 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

to allow filtergraph printing to access the information.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_filter.c | 190 +-------------------------------
 fftools/ffmpeg_filter.h | 234 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+), 189 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index d314aec206..eab9487f97 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ffmpeg.h"
+#include "ffmpeg_filter.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -42,44 +43,6 @@
 // FIXME private header, used for mid_pred()
 #include "libavcodec/mathops.h"
 
-typedef struct FilterGraphPriv {
-    FilterGraph      fg;
-
-    // name used for logging
-    char             log_name[32];
-
-    int              is_simple;
-    // true when the filtergraph contains only meta filters
-    // that do not modify the frame data
-    int              is_meta;
-    // source filters are present in the graph
-    int              have_sources;
-    int              disable_conversions;
-
-    unsigned         nb_outputs_done;
-
-    const char      *graph_desc;
-
-    int              nb_threads;
-
-    // frame for temporarily holding output from the filtergraph
-    AVFrame         *frame;
-    // frame for sending output to the encoder
-    AVFrame         *frame_enc;
-
-    Scheduler       *sch;
-    unsigned         sch_idx;
-} FilterGraphPriv;
-
-static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
-{
-    return (FilterGraphPriv*)fg;
-}
-
-static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
-{
-    return (const FilterGraphPriv*)fg;
-}
 
 // data that is local to the filter thread and not visible outside of it
 typedef struct FilterGraphThread {
@@ -102,157 +65,6 @@ typedef struct FilterGraphThread {
     uint8_t         *eof_out;
 } FilterGraphThread;
 
-typedef struct InputFilterPriv {
-    InputFilter         ifilter;
-
-    InputFilterOptions  opts;
-
-    int                 index;
-
-    AVFilterContext    *filter;
-
-    // used to hold submitted input
-    AVFrame            *frame;
-
-    /* for filters that are not yet bound to an input stream,
-     * this stores the input linklabel, if any */
-    uint8_t            *linklabel;
-
-    // filter data type
-    enum AVMediaType    type;
-    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
-    // same as type otherwise
-    enum AVMediaType    type_src;
-
-    int                 eof;
-    int                 bound;
-    int                 drop_warned;
-    uint64_t            nb_dropped;
-
-    // parameters configured for this input
-    int                 format;
-
-    int                 width, height;
-    AVRational          sample_aspect_ratio;
-    enum AVColorSpace   color_space;
-    enum AVColorRange   color_range;
-
-    int                 sample_rate;
-    AVChannelLayout     ch_layout;
-
-    AVRational          time_base;
-
-    AVFrameSideData   **side_data;
-    int                 nb_side_data;
-
-    AVFifo             *frame_queue;
-
-    AVBufferRef        *hw_frames_ctx;
-
-    int                 displaymatrix_present;
-    int                 displaymatrix_applied;
-    int32_t             displaymatrix[9];
-
-    int                 downmixinfo_present;
-    AVDownmixInfo       downmixinfo;
-
-    struct {
-        AVFrame *frame;
-
-        int64_t last_pts;
-        int64_t end_pts;
-
-        /// marks if sub2video_update should force an initialization
-        unsigned int initialize;
-    } sub2video;
-} InputFilterPriv;
-
-static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
-{
-    return (InputFilterPriv*)ifilter;
-}
-
-typedef struct FPSConvContext {
-    AVFrame          *last_frame;
-    /* number of frames emitted by the video-encoding sync code */
-    int64_t           frame_number;
-    /* history of nb_frames_prev, i.e. the number of times the
-     * previous frame was duplicated by vsync code in recent
-     * do_video_out() calls */
-    int64_t           frames_prev_hist[3];
-
-    uint64_t          dup_warning;
-
-    int               last_dropped;
-    int               dropped_keyframe;
-
-    enum VideoSyncMethod vsync_method;
-
-    AVRational        framerate;
-    AVRational        framerate_max;
-    const AVRational *framerate_supported;
-    int               framerate_clip;
-} FPSConvContext;
-
-typedef struct OutputFilterPriv {
-    OutputFilter            ofilter;
-
-    int                     index;
-
-    void                   *log_parent;
-    char                    log_name[32];
-
-    char                   *name;
-
-    AVFilterContext        *filter;
-
-    /* desired output stream properties */
-    int                     format;
-    int                     width, height;
-    int                     sample_rate;
-    AVChannelLayout         ch_layout;
-    enum AVColorSpace       color_space;
-    enum AVColorRange       color_range;
-
-    AVFrameSideData       **side_data;
-    int                     nb_side_data;
-
-    // time base in which the output is sent to our downstream
-    // does not need to match the filtersink's timebase
-    AVRational              tb_out;
-    // at least one frame with the above timebase was sent
-    // to our downstream, so it cannot change anymore
-    int                     tb_out_locked;
-
-    AVRational              sample_aspect_ratio;
-
-    AVDictionary           *sws_opts;
-    AVDictionary           *swr_opts;
-
-    // those are only set if no format is specified and the encoder gives us multiple options
-    // They point directly to the relevant lists of the encoder.
-    const int              *formats;
-    const AVChannelLayout  *ch_layouts;
-    const int              *sample_rates;
-    const enum AVColorSpace *color_spaces;
-    const enum AVColorRange *color_ranges;
-
-    AVRational              enc_timebase;
-    int64_t                 trim_start_us;
-    int64_t                 trim_duration_us;
-    // offset for output timestamps, in AV_TIME_BASE_Q
-    int64_t                 ts_offset;
-    int64_t                 next_pts;
-    FPSConvContext          fps;
-
-    unsigned                flags;
-} OutputFilterPriv;
-
-static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
-{
-    return (OutputFilterPriv*)ofilter;
-}
-
 typedef struct FilterCommand {
     char *target;
     char *command;
diff --git a/fftools/ffmpeg_filter.h b/fftools/ffmpeg_filter.h
new file mode 100644
index 0000000000..94b94beece
--- /dev/null
+++ b/fftools/ffmpeg_filter.h
@@ -0,0 +1,234 @@
+/*
+ * 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_FFMPEG_FILTER_H
+#define FFTOOLS_FFMPEG_FILTER_H
+
+#include "ffmpeg.h"
+
+#include <stdint.h>
+
+#include "ffmpeg_sched.h"
+#include "sync_queue.h"
+
+#include "libavfilter/avfilter.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/dict.h"
+#include "libavutil/fifo.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/downmix_info.h"
+
+typedef struct FilterGraphPriv {
+    FilterGraph      fg;
+
+    // name used for logging
+    char             log_name[32];
+
+    int              is_simple;
+    // true when the filtergraph contains only meta filters
+    // that do not modify the frame data
+    int              is_meta;
+    // source filters are present in the graph
+    int              have_sources;
+    int              disable_conversions;
+
+    unsigned         nb_outputs_done;
+
+    const char      *graph_desc;
+
+    int              nb_threads;
+
+    // frame for temporarily holding output from the filtergraph
+    AVFrame         *frame;
+    // frame for sending output to the encoder
+    AVFrame         *frame_enc;
+
+    Scheduler       *sch;
+    unsigned         sch_idx;
+
+    AVBPrint graph_print_buf;
+
+} FilterGraphPriv;
+
+static inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
+{
+    return (FilterGraphPriv*)fg;
+}
+
+static inline const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
+{
+    return (const FilterGraphPriv*)fg;
+}
+
+typedef struct InputFilterPriv {
+    InputFilter         ifilter;
+
+    InputFilterOptions  opts;
+
+    int                 index;
+
+    AVFilterContext    *filter;
+
+    // used to hold submitted input
+    AVFrame            *frame;
+
+    /* for filters that are not yet bound to an input stream,
+     * this stores the input linklabel, if any */
+    uint8_t            *linklabel;
+
+    // filter data type
+    enum AVMediaType    type;
+    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
+    // same as type otherwise
+    enum AVMediaType    type_src;
+
+    int                 eof;
+    int                 bound;
+    int                 drop_warned;
+    uint64_t            nb_dropped;
+
+    // parameters configured for this input
+    int                 format;
+
+    int                 width, height;
+    AVRational          sample_aspect_ratio;
+    enum AVColorSpace   color_space;
+    enum AVColorRange   color_range;
+
+    int                 sample_rate;
+    AVChannelLayout     ch_layout;
+
+    AVRational          time_base;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
+    AVFifo             *frame_queue;
+
+    AVBufferRef        *hw_frames_ctx;
+
+    int                 displaymatrix_present;
+    int                 displaymatrix_applied;
+    int32_t             displaymatrix[9];
+
+    int                 downmixinfo_present;
+    AVDownmixInfo       downmixinfo;
+
+    struct {
+        AVFrame *frame;
+
+        int64_t last_pts;
+        int64_t end_pts;
+
+        /// marks if sub2video_update should force an initialization
+        unsigned int initialize;
+    } sub2video;
+} InputFilterPriv;
+
+static inline InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
+{
+    return (InputFilterPriv*)ifilter;
+}
+
+typedef struct FPSConvContext {
+    AVFrame          *last_frame;
+    /* number of frames emitted by the video-encoding sync code */
+    int64_t           frame_number;
+    /* history of nb_frames_prev, i.e. the number of times the
+     * previous frame was duplicated by vsync code in recent
+     * do_video_out() calls */
+    int64_t           frames_prev_hist[3];
+
+    uint64_t          dup_warning;
+
+    int               last_dropped;
+    int               dropped_keyframe;
+
+    enum VideoSyncMethod vsync_method;
+
+    AVRational        framerate;
+    AVRational        framerate_max;
+    const AVRational *framerate_supported;
+    int               framerate_clip;
+} FPSConvContext;
+
+
+typedef struct OutputFilterPriv {
+    OutputFilter            ofilter;
+
+    int                     index;
+
+    void                   *log_parent;
+    char                    log_name[32];
+
+    char                   *name;
+
+    AVFilterContext        *filter;
+
+    /* desired output stream properties */
+    int                     format;
+    int                     width, height;
+    int                     sample_rate;
+    AVChannelLayout         ch_layout;
+    enum AVColorSpace       color_space;
+    enum AVColorRange       color_range;
+
+    AVFrameSideData       **side_data;
+    int                     nb_side_data;
+
+    // time base in which the output is sent to our downstream
+    // does not need to match the filtersink's timebase
+    AVRational              tb_out;
+    // at least one frame with the above timebase was sent
+    // to our downstream, so it cannot change anymore
+    int                     tb_out_locked;
+
+    AVRational              sample_aspect_ratio;
+
+    AVDictionary           *sws_opts;
+    AVDictionary           *swr_opts;
+
+    // those are only set if no format is specified and the encoder gives us multiple options
+    // They point directly to the relevant lists of the encoder.
+    const int              *formats;
+    const AVChannelLayout  *ch_layouts;
+    const int              *sample_rates;
+    const enum AVColorSpace *color_spaces;
+    const enum AVColorRange *color_ranges;
+
+    AVRational              enc_timebase;
+    int64_t                 trim_start_us;
+    int64_t                 trim_duration_us;
+    // offset for output timestamps, in AV_TIME_BASE_Q
+    int64_t                 ts_offset;
+    int64_t                 next_pts;
+    FPSConvContext          fps;
+
+    unsigned                flags;
+} OutputFilterPriv;
+
+static inline OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
+{
+    return (OutputFilterPriv*)ofilter;
+}
+
+#endif /* FFTOOLS_FFMPEG_FILTER_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH 6/9] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
                   ` (4 preceding siblings ...)
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 5/9] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-04-14 12:47 ` softworkz
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 7/9] fftools/resources: Add resource manager files softworkz
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-14 12:47 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/APIchanges         |  3 +++
 libavfilter/avfilter.c |  9 +++++++++
 libavfilter/avfilter.h | 12 ++++++++++++
 3 files changed, 24 insertions(+)

diff --git a/doc/APIchanges b/doc/APIchanges
index 65bf5a9419..32374f5453 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
+  Add avfilter_link_get_hw_frames_ctx().
+
 2025-04-07 - 19e9a203b7 - lavu 60.01.100 - dict.h
   Add AV_DICT_DEDUP.
 
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index 64c1075c40..c76d43a215 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -989,6 +989,15 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx)
     return pads[pad_idx].type;
 }
 
+AVBufferRef *avfilter_link_get_hw_frames_ctx(AVFilterLink *link)
+{
+    FilterLink *plink = ff_filter_link(link);
+    if (plink->hw_frames_ctx)
+        return av_buffer_ref(plink->hw_frames_ctx);
+
+    return NULL;
+}
+
 static int default_filter_frame(AVFilterLink *link, AVFrame *frame)
 {
     return ff_filter_frame(link->dst->outputs[0], frame);
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index a89d3cf658..f85929dc5c 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -96,6 +96,18 @@ const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
  */
 enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
 
+/**
+ * Get the hardware frames context of a filter link.
+ *
+ * @param link an AVFilterLink
+ *
+ * @return a ref-counted copy of the link's hw_frames_ctx field if there is
+ *         a hardware frames context associated with the link or NULL otherwise.
+ *         The returned AVBufferRef needs to be released with av_buffer_unref()
+ *         when it is no longer used.
+ */
+AVBufferRef* avfilter_link_get_hw_frames_ctx(AVFilterLink *link);
+
 /**
  * Lists of formats / etc. supported by an end of a link.
  *
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH 7/9] fftools/resources: Add resource manager files
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
                   ` (5 preceding siblings ...)
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 6/9] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
@ 2025-04-14 12:47 ` softworkz
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 8/9] fftools/graphprint: Add execution graph printing softworkz
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-14 12:47 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 ffbuild/common.mak           |  28 ++-
 fftools/resources/.gitignore |   4 +
 fftools/resources/Makefile   |  27 +++
 fftools/resources/graph.css  | 353 +++++++++++++++++++++++++++++++++++
 fftools/resources/graph.html |  86 +++++++++
 fftools/resources/resman.c   | 213 +++++++++++++++++++++
 fftools/resources/resman.h   |  50 +++++
 7 files changed, 760 insertions(+), 1 deletion(-)
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h

diff --git a/ffbuild/common.mak b/ffbuild/common.mak
index ca45a0f368..eb68796001 100644
--- a/ffbuild/common.mak
+++ b/ffbuild/common.mak
@@ -139,6 +139,32 @@ else
 	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
 endif
 
+# 1) Preprocess CSS to a minified version
+%.css.min: %.css
+	# Must start with a tab in the real Makefile
+	sed 's!/\\*.*\\*/!!g' $< \
+	| tr '\n' ' ' \
+	| tr -s ' ' \
+	| sed 's/^ //; s/ $$//' \
+	> $@
+
+# 2) Gzip the minified CSS
+%.css.min.gz: %.css.min
+	$(M)gzip -nc9 $< > $@
+
+# 3) Convert the gzipped CSS to a .c array
+%.css.c: %.css.min.gz $(BIN2CEXE)
+	$(BIN2C) $< $@ $(subst .,_,$(basename $(notdir $@)))
+
+# 4) Gzip the HTML file (no minification needed)
+%.html.gz: TAG = GZIP
+%.html.gz: %.html
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) > $@
+
+# 5) Convert the gzipped HTML to a .c array
+%.html.c: %.html.gz $(BIN2CEXE)
+	$(BIN2C) $< $@ $(subst .,_,$(basename $(notdir $@)))
+
 clean::
 	$(RM) $(BIN2CEXE) $(CLEANSUFFIXES:%=ffbuild/%)
 
@@ -214,7 +240,7 @@ $(TOOLOBJS): | tools
 
 OUTDIRS := $(OUTDIRS) $(dir $(OBJS) $(HOBJS) $(HOSTOBJS) $(SLIBOBJS) $(SHLIBOBJS) $(STLIBOBJS) $(TESTOBJS))
 
-CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
+CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *.html.gz *.html.c *.css.gz *.css.c  *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
 LIBSUFFIXES       = *.a *.lib *.so *.so.* *.dylib *.dll *.def *.dll.a
 
 define RULES
diff --git a/fftools/resources/.gitignore b/fftools/resources/.gitignore
new file mode 100644
index 0000000000..5f496535a6
--- /dev/null
+++ b/fftools/resources/.gitignore
@@ -0,0 +1,4 @@
+*.html.c
+*.css.c
+*.html.gz
+*.css.gz
diff --git a/fftools/resources/Makefile b/fftools/resources/Makefile
new file mode 100644
index 0000000000..f3a0d0a970
--- /dev/null
+++ b/fftools/resources/Makefile
@@ -0,0 +1,27 @@
+clean::
+	$(RM) $(CLEANSUFFIXES:%=fftools/resources/%)
+
+
+HTML_RESOURCES := fftools/resources/graph.html \
+
+# .html => (gzip) .html.gz => (bin2c) .html.c => (cc) .o
+HTML_RESOURCES_GZ := $(HTML_RESOURCES:.html=.html.gz)
+HTML_RESOURCES_C := $(HTML_RESOURCES_GZ:.html.gz=.html.c)
+HTML_RESOURCES_OBJS := $(HTML_RESOURCES_C:.c=.o)
+
+CSS_RESOURCES := fftools/resources/graph.css   \
+
+# .css => (sh) .css.min  => (gzip) .css.min.gz => (bin2c) .css.c => (cc) .o
+CSS_RESOURCES_MIN := $(CSS_RESOURCES:.css=.css.min)
+CSS_RESOURCES_GZ := $(CSS_RESOURCES_MIN:.css.min=.css.min.gz)
+CSS_RESOURCES_C := $(CSS_RESOURCES_GZ:.css.min.gz=.css.c)
+CSS_RESOURCES_OBJS := $(CSS_RESOURCES_C:.c=.o)
+
+# Uncomment to prevent deletion
+#.PRECIOUS: %.css.c %.css.min %.css.gz  %.css.min.gz
+
+OBJS-resman +=                  \
+    fftools/resources/resman.o          \
+    $(HTML_RESOURCES_OBJS)      \
+    $(CSS_RESOURCES_OBJS)       \
+
diff --git a/fftools/resources/graph.css b/fftools/resources/graph.css
new file mode 100644
index 0000000000..ab480673ab
--- /dev/null
+++ b/fftools/resources/graph.css
@@ -0,0 +1,353 @@
+/* Variables */
+.root {
+    --ff-colvideo: #6eaa7b;
+    --ff-colaudio: #477fb3;
+    --ff-colsubtitle: #ad76ab;
+    --ff-coltext: #666;
+}
+
+/* Common & Misc */
+.ff-inputfiles rect, .ff-outputfiles rect, .ff-inputstreams rect, .ff-outputstreams rect, .ff-decoders rect, .ff-encoders rect {
+    stroke-width: 0;
+    stroke: transparent;
+    filter: none !important;
+    fill: transparent !important;
+    display: none !important;
+}
+
+.cluster span {
+    color: var(--ff-coltext);
+}
+
+.cluster rect {
+    stroke: #dfdfdf !important;
+    transform: translateY(-2.3rem);
+    filter: drop-shadow(1px 2px 2px rgba(185,185,185,0.2)) !important;
+    rx: 8;
+    ry: 8;
+}
+
+.cluster-label {
+    font-size: 1.1rem;
+}
+
+    .cluster-label .nodeLabel {
+        display: block;
+        font-weight: 500;
+        color: var(--ff-coltext);
+    }
+
+    .cluster-label div {
+        max-width: unset !important;
+        padding: 3px;
+    }
+
+    .cluster-label foreignObject {
+        transform: translateY(-0.7rem);
+    }
+
+/* Input and output files */
+.node.ff-inputfile .label foreignObject, .node.ff-outputfile .label foreignObject {
+    overflow: visible;
+}
+
+.cluster.ff-inputfile .cluster-label foreignObject div:not(foreignObject div div), .cluster.ff-outputfile .cluster-label foreignObject div:not(foreignObject div div) {
+    display: table !important;
+}
+
+.nodeLabel div.ff-inputfile, .nodeLabel div.ff-outputfile {
+    font-size: 1.1rem;
+    font-weight: 500;
+    min-width: 14rem;
+    width: 100%;
+    display: flex;
+    color: var(--ff-coltext);
+    margin-top: 0.1rem;
+    line-height: 1.35;
+    padding-bottom: 1.9rem;
+}
+
+.nodeLabel div.ff-outputfile {
+    flex-direction: row-reverse;
+}
+
+.ff-inputfile .index, .ff-outputfile .index {
+    order: 2;
+    color: var(--ff-coltext);
+    text-align: center;
+    border-radius: 0.45rem;
+    border: 0.18em solid #666666db;
+    font-weight: 600;
+    padding: 0 0.3em;
+    opacity: 0.8;
+}
+
+    .ff-inputfile .index::before {
+        content: 'In ';
+    }
+
+    .ff-outputfile .index::before {
+        content: 'Out ';
+    }
+
+.ff-inputfile .demuxer_name, .ff-outputfile .muxer_name {
+    flex: 1;
+    order: 1;
+    font-size: 0.9rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: center;
+    max-width: 8rem;
+    align-content: center;
+    margin: 0.2rem 0.4rem 0 0.4rem;
+}
+
+.ff-inputfile .file_extension, .ff-outputfile .file_extension {
+    order: 0;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.45rem;
+    font-weight: 600;
+    padding: 0 0.4em;
+    align-content: center;
+    opacity: 0.8;
+}
+
+.ff-inputfile .url, .ff-outputfile .url {
+    order: 4;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0.75rem;
+    font-size: 0.7rem;
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin: 0 0.3rem;
+    direction: rtl;
+    color: #999;
+}
+
+.cluster.ff-inputfile rect, .cluster.ff-outputfile rect {
+    transform: translateY(-1.8rem);
+    fill: url(#ff-radgradient);
+}
+
+/* Input and output streams */
+.node.ff-inputstream rect, .node.ff-outputstream rect {
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    fill: white;
+    stroke: #e5e5e5 !important;
+    height: 2.7rem;
+    transform: translateY(0.2rem);
+    filter: none;
+    rx: 3;
+    ry: 3;
+}
+
+.node.ff-inputstream .label foreignObject, .node.ff-outputstream .label foreignObject {
+    transform: translateY(-0.2%);
+    overflow: visible;
+}
+
+    .node.ff-inputstream .label foreignObject div:not(foreignObject div div), .node.ff-outputstream .label foreignObject div:not(foreignObject div div) {
+        display: block !important;
+        line-height: 1.5 !important;
+    }
+
+.nodeLabel div.ff-inputstream, .nodeLabel div.ff-outputstream {
+    font-size: 1.0rem;
+    font-weight: 500;
+    min-width: 12rem;
+    width: 100%;
+    display: flex;
+}
+
+.nodeLabel div.ff-outputstream {
+    flex-direction: row-reverse;
+}
+
+.ff-inputstream .name, .ff-outputstream .name {
+    flex: 1;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: left;
+    align-content: center;
+    margin-bottom: -0.15rem;
+}
+
+.ff-inputstream .index, .ff-outputstream .index {
+    flex: 0 0 1.4rem;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.3rem;
+    font-weight: 600;
+    margin-right: -0.3rem;
+    margin-left: 0.4rem;
+    opacity: 0.8;
+}
+
+.ff-outputstream .index {
+    margin-right: 0.6rem;
+    margin-left: -0.4rem;
+}
+
+.ff-inputstream::before, .ff-outputstream::before {
+    font-variant-emoji: text;
+    flex: 0 0 2rem;
+    margin-left: -0.8rem;
+    margin-right: 0.2rem;
+}
+
+.ff-outputstream::before {
+    margin-left: 0.2rem;
+    margin-right: -0.6rem;
+}
+
+.ff-inputstream.video::before, .ff-outputstream.video::before {
+    content: '\239A';
+    color: var(--ff-colvideo);
+    font-size: 2.25rem;
+    line-height: 0.5;
+    font-weight: bold;
+}
+
+.ff-inputstream.audio::before, .ff-outputstream.audio::before {
+    content: '\1F39D';
+    color: var(--ff-colaudio);
+    font-size: 1.75rem;
+    line-height: 0.9;
+}
+
+.ff-inputstream.subtitle::before, .ff-outputstream.subtitle::before {
+    content: '\1AC';
+    color: var(--ff-colsubtitle);
+    font-size: 1.2rem;
+    line-height: 1.1;
+    transform: scaleX(1.5);
+    margin-top: 0.050rem;
+}
+
+.ff-inputstream.attachment::before, .ff-outputstream.attachment::before {
+    content: '\1F4CE';
+    font-size: 1.3rem;
+    line-height: 1.15;
+}
+
+.ff-inputstream.data::before, .ff-outputstream.data::before {
+    content: '\27E8\2219\2219\2219\27E9';
+    font-size: 1.15rem;
+    line-height: 1.17;
+    letter-spacing: -0.3px;
+}
+
+/* Filter Graphs */
+.cluster.ff-filters rect {
+    stroke-dasharray: 6 !important;
+    stroke-width: 1.3px;
+    stroke: #d1d1d1 !important;
+    filter: none !important;
+}
+
+.cluster.ff-filters div.ff-filters .id {
+    display: none;
+}
+
+.cluster.ff-filters div.ff-filters .name {
+    margin-right: 0.5rem;
+    font-size: 0.9rem;
+}
+
+.cluster.ff-filters div.ff-filters .description {
+    font-weight: 400;
+    font-size: 0.75rem;
+    vertical-align: middle;
+    color: #777;
+    font-family: Cascadia Code, Lucida Console, monospace;
+}
+
+/* Filter Shapes */
+.node.ff-filter rect {
+    rx: 10;
+    ry: 10;
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.node.ff-filter .label foreignObject {
+    transform: translateY(-0.4rem);
+    overflow: visible;
+}
+
+.nodeLabel div.ff-filter {
+    font-size: 1.0rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    min-width: 5.5rem;
+    margin-bottom: 0.5rem;
+}
+
+    .nodeLabel div.ff-filter span {
+        color: inherit;
+    }
+
+/* Decoders & Encoders */
+.node.ff-decoder rect, .node.ff-encoder rect {
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.nodeLabel div.ff-decoder, .nodeLabel div.ff-encoder {
+    font-size: 0.85rem;
+    font-weight: 500;
+    min-width: 3.5rem;
+}
+
+/* Links and Arrows */
+path.flowchart-link[id|='video'] {
+    stroke: var(--ff-colvideo);
+}
+
+path.flowchart-link[id|='audio'] {
+    stroke: var(--ff-colaudio);
+}
+
+path.flowchart-link[id|='subtitle'] {
+    stroke: var(--ff-colsubtitle);
+}
+
+marker.marker path {
+    fill: context-stroke;
+}
+
+.edgeLabel foreignObject {
+    transform: translateY(-1rem);
+}
+
+.edgeLabel p {
+    background: transparent;
+    white-space: nowrap;
+    margin: 1rem 0.5rem !important;
+    font-weight: 500;
+    color: var(--ff-coltext);
+}
+
+.edgeLabel, .labelBkg {
+    background: transparent;
+}
+
+.edgeLabels .edgeLabel * {
+    font-size: 0.8rem;
+}
diff --git a/fftools/resources/graph.html b/fftools/resources/graph.html
new file mode 100644
index 0000000000..cd94276fd4
--- /dev/null
+++ b/fftools/resources/graph.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <title>FFmpeg Graph</title>
+</head>
+<body>
+<style>
+    html {
+        color: #666;
+        font-family: Roboto;
+        height: 100%;
+    }
+
+    body {
+        background-color: #f9f9f9;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        margin: 0;
+        padding: 1.7rem 1.7rem 3.5rem 1.7rem;
+    }
+
+    div#banner {
+        align-items: center;
+        display: flex;
+        flex-direction: row;
+        margin-bottom: 1.5rem;
+        margin-left: 0.6vw;
+    }
+
+    div#header {
+        aspect-ratio: 1/1;
+        background-image: url(https://trac.ffmpeg.org/ffmpeg-logo.png);
+        background-size: cover;
+        width: 1.6rem;
+    }
+
+    h1 {
+        font-size: 1.2rem;
+        margin: 0 0.5rem;
+    }
+
+    pre.mermaid {
+        align-items: center;
+        background-color: white;
+        box-shadow: 2px 2px 25px 0px #00000010;
+        color: transparent;
+        display: flex;
+        flex: 1;
+        justify-content: center;
+        margin: 0;
+        overflow: overlay;
+    }
+
+    pre.mermaid svg {
+        height: auto;
+        margin: 0;
+        max-width: unset !important;
+        width: auto;
+    }
+
+    pre.mermaid svg * {
+        user-select: none;
+    }
+</style>
+<div id="banner">
+    <div id="header"></div>
+    <h1>FFmpeg Execution Graph</h1>
+</div>
+<pre class="mermaid">
+__###__
+</pre>
+<script type="module">
+        import vanillaJsWheelZoom from 'https://cdn.jsdelivr.net/npm/vanilla-js-wheel-zoom@9.0.4/+esm';
+        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
+        function initViewer() {
+            var element = document.querySelector('.mermaid svg')
+            vanillaJsWheelZoom.create('pre.mermaid svg', { type: 'html', smoothTimeDrag: 0, width: element.clientWidth, height: element.clientHeight, maxScale: 3 });
+        }
+        mermaid.initialize({ startOnLoad: false }); 
+        document.fonts.ready.then(() => { mermaid.run({ querySelector: '.mermaid', postRenderCallback: initViewer }); });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/fftools/resources/resman.c b/fftools/resources/resman.c
new file mode 100644
index 0000000000..488aaeecf6
--- /dev/null
+++ b/fftools/resources/resman.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <zlib.h>
+#include "resman.h"
+#include <libavformat/url.h>
+#include "fftools/ffmpeg_filter.h"
+#include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+
+extern const unsigned char ff_graph_html_data[];
+extern const unsigned int ff_graph_html_len;
+
+extern const unsigned char ff_graph_css_data[];
+extern const unsigned ff_graph_css_len;
+
+static const FFResourceDefinition resource_definitions[] = {
+    [FF_RESOURCE_GRAPH_CSS]   = { FF_RESOURCE_GRAPH_CSS,   "graph.css",   &ff_graph_css_data[0],   &ff_graph_css_len   },
+    [FF_RESOURCE_GRAPH_HTML]  = { FF_RESOURCE_GRAPH_HTML,  "graph.html",  &ff_graph_html_data[0],  &ff_graph_html_len  },
+};
+
+
+static const AVClass resman_class = {
+    .class_name = "ResourceManager",
+};
+
+typedef struct ResourceManagerContext {
+    const AVClass *class;
+    AVDictionary *resource_dic;
+} ResourceManagerContext;
+
+static AVMutex mutex = AV_MUTEX_INITIALIZER;
+
+ResourceManagerContext *resman_ctx = NULL;
+
+
+static int decompress_gzip(ResourceManagerContext *ctx, uint8_t *in, unsigned in_len, char **out, size_t *out_len)
+{
+    z_stream strm;
+    unsigned chunk = 65534;
+    int ret;
+    uint8_t *buf;
+
+    *out = NULL;
+    memset(&strm, 0, sizeof(strm));
+
+    // Allocate output buffer with extra byte for null termination
+    buf = (uint8_t *)av_mallocz(chunk + 1);
+    if (!buf) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to allocate decompression buffer\n");
+        return AVERROR(ENOMEM);
+    }
+
+    // 15 + 16 tells zlib to detect GZIP or zlib automatically
+    ret = inflateInit2(&strm, 15 + 16);
+    if (ret != Z_OK) {
+        av_log(ctx, AV_LOG_ERROR, "Error during zlib initialization: %s\n", strm.msg);
+        av_free(buf);
+        return AVERROR(ENOSYS);
+    }
+
+    strm.avail_in  = in_len;
+    strm.next_in   = in;
+    strm.avail_out = chunk;
+    strm.next_out  = buf;
+
+    ret = inflate(&strm, Z_FINISH);
+    if (ret != Z_OK && ret != Z_STREAM_END) {
+        av_log(ctx, AV_LOG_ERROR, "Inflate failed: %d, %s\n", ret, strm.msg);
+        inflateEnd(&strm);
+        av_free(buf);
+        return (ret == Z_STREAM_END) ? Z_OK : ((ret == Z_OK) ? Z_BUF_ERROR : ret);
+    }
+
+    if (strm.avail_out == 0) {
+        // TODO: Error or loop decoding?
+        av_log(ctx, AV_LOG_WARNING, "Decompression buffer may be too small\n");
+    }
+
+    *out_len = chunk - strm.avail_out;
+    buf[*out_len] = 0; // Ensure null termination
+
+    inflateEnd(&strm);
+    *out = (char *)buf;
+    return Z_OK;
+}
+
+static ResourceManagerContext *get_resman_context(void)
+{
+    ResourceManagerContext *res = resman_ctx;
+
+    ff_mutex_lock(&mutex);
+
+    if (res)
+        goto end;
+
+    res = av_mallocz(sizeof(ResourceManagerContext));
+    if (!res) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to allocate resource manager context\n");
+        goto end;
+    }
+
+    res->class = &resman_class;
+    resman_ctx = res;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
+
+
+void ff_resman_uninit(void)
+{
+    ff_mutex_lock(&mutex);
+
+    if (resman_ctx) {
+        if (resman_ctx->resource_dic)
+            av_dict_free(&resman_ctx->resource_dic);
+        av_freep(&resman_ctx);
+    }
+
+    ff_mutex_unlock(&mutex);
+}
+
+
+char *ff_resman_get_string(FFResourceId resource_id)
+{
+    ResourceManagerContext *ctx               = get_resman_context();
+    FFResourceDefinition resource_definition = { 0 };
+    AVDictionaryEntry *dic_entry;
+    char *res = NULL;
+
+    if (!ctx)
+        return NULL;
+
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(resource_definitions); ++i) {
+        FFResourceDefinition def = resource_definitions[i];
+        if (def.resource_id == resource_id) {
+            resource_definition = def;
+            break;
+        }
+    }
+
+    if (!resource_definition.name) {
+        av_log(ctx, AV_LOG_ERROR, "Unable to find resource with ID %d\n", resource_id);
+        return NULL;
+    }
+
+    ff_mutex_lock(&mutex);
+
+    dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+    if (!dic_entry) {
+        char *out = NULL;
+        size_t out_len;
+        int dict_ret;
+
+        int ret = decompress_gzip(ctx, (uint8_t *)resource_definition.data, *resource_definition.data_len, &out, &out_len);
+
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to decompress the resource with ID %d\n", resource_id);
+            goto end;
+        }
+
+        dict_ret = av_dict_set(&ctx->resource_dic, resource_definition.name, out, 0);
+        if (dict_ret < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to store decompressed resource in dictionary: %d\n", dict_ret);
+            av_freep(&out);
+            goto end;
+        }
+
+        av_freep(&out);
+        dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+        if (!dic_entry) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to retrieve resource from dictionary after storing it\n");
+            goto end;
+        }
+    }
+
+    res = dic_entry->value;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
diff --git a/fftools/resources/resman.h b/fftools/resources/resman.h
new file mode 100644
index 0000000000..6485db5091
--- /dev/null
+++ b/fftools/resources/resman.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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_RESOURCES_RESMAN_H
+#define FFTOOLS_RESOURCES_RESMAN_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "fftools/ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+#include "fftools/textformat/avtextformat.h"
+
+typedef enum {
+    FF_RESOURCE_GRAPH_CSS,
+    FF_RESOURCE_GRAPH_HTML,
+} FFResourceId;
+
+typedef struct FFResourceDefinition {
+    FFResourceId resource_id;
+    const char *name;
+
+    const unsigned char *data;
+    const unsigned *data_len;
+
+} FFResourceDefinition;
+
+void ff_resman_uninit(void);
+
+char *ff_resman_get_string(FFResourceId resource_id);
+
+#endif /* FFTOOLS_RESOURCES_RESMAN_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH 8/9] fftools/graphprint: Add execution graph printing
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
                   ` (6 preceding siblings ...)
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 7/9] fftools/resources: Add resource manager files softworkz
@ 2025-04-14 12:47 ` softworkz
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 9/9] fftools/graphprint: Now, make it a Killer-Feature! softworkz
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
  9 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-14 12:47 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

The key benefits are:

- Different to other graph printing methods, this is outputting:
  - all graphs with runtime state
    (including auto-inserted filters)
  - each graph with its inputs and outputs
  - all filters with their in- and output pads
  - all connections between all input- and output pads
  - for each connection:
    - the runtime-negotiated format and media type
    - the hw context
    - if video hw context, both: hw pixfmt + sw pixfmt
- Output can either be printed to stdout or written to specified file
- Output is machine-readable
- Use the same output implementation as ffprobe, supporting multiple
  formats

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi                   |   10 +
 fftools/Makefile                  |   17 +
 fftools/ffmpeg.c                  |    4 +
 fftools/ffmpeg.h                  |    3 +
 fftools/ffmpeg_filter.c           |    5 +
 fftools/ffmpeg_opt.c              |   13 +
 fftools/graph/graphprint.c        | 1102 +++++++++++++++++++++++++++++
 fftools/graph/graphprint.h        |   30 +
 fftools/textformat/avtextformat.c |    2 +
 fftools/textformat/avtextformat.h |   29 +
 fftools/textformat/tf_mermaid.c   |  655 +++++++++++++++++
 fftools/textformat/tf_mermaid.h   |   41 ++
 12 files changed, 1911 insertions(+)
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 17ba876ea3..35675b5309 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1394,6 +1394,16 @@ It is on by default, to explicitly disable it you need to specify @code{-nostats
 @item -stats_period @var{time} (@emph{global})
 Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
 
+@item -print_graphs (@emph{global})
+Prints execution graph details to stderr in the format set via -print_graphs_format.
+
+@item -print_graphs_file @var{filename} (@emph{global})
+Writes execution graph details to the specified file in the format set via -print_graphs_format.
+
+@item -print_graphs_format @var{format} (@emph{global})
+Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
+The default format is json.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index e9c9891c34..8d87ea8255 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -9,6 +9,8 @@ AVBASENAMES  = ffmpeg ffplay ffprobe
 ALLAVPROGS   = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
 ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
 
+include $(SRC_PATH)/fftools/resources/Makefile
+
 OBJS-ffmpeg +=                  \
     fftools/ffmpeg_dec.o        \
     fftools/ffmpeg_demux.o      \
@@ -19,8 +21,21 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
+    fftools/graph/graphprint.o        \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
+    fftools/textformat/avtextformat.o \
+    fftools/textformat/tf_compact.o   \
+    fftools/textformat/tf_default.o   \
+    fftools/textformat/tf_flat.o      \
+    fftools/textformat/tf_ini.o       \
+    fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
+    fftools/textformat/tf_xml.o       \
+    fftools/textformat/tw_avio.o      \
+    fftools/textformat/tw_buffer.o    \
+    fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffprobe +=                       \
     fftools/textformat/avtextformat.o \
@@ -29,10 +44,12 @@ OBJS-ffprobe +=                       \
     fftools/textformat/tf_flat.o      \
     fftools/textformat/tf_ini.o       \
     fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
     fftools/textformat/tf_xml.o       \
     fftools/textformat/tw_avio.o      \
     fftools/textformat/tw_buffer.o    \
     fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffplay += fftools/ffplay_renderer.o
 
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..6766ec209c 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -81,6 +81,7 @@
 #include "ffmpeg.h"
 #include "ffmpeg_sched.h"
 #include "ffmpeg_utils.h"
+#include "graph/graphprint.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
+    if (print_graphs || print_graphs_file)
+        print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
+
     if (do_benchmark) {
         int64_t maxrss = getmaxrss() / 1024;
         av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 5869979214..7fbf0ad532 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -717,6 +717,9 @@ extern float max_error_rate;
 extern char *filter_nbthreads;
 extern int filter_complex_nbthreads;
 extern int vstats_version;
+extern int print_graphs;
+extern char *print_graphs_file;
+extern char *print_graphs_format;
 extern int auto_conversion_filters;
 
 extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index eab9487f97..b774606562 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -22,6 +22,7 @@
 
 #include "ffmpeg.h"
 #include "ffmpeg_filter.h"
+#include "graph/graphprint.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -2983,6 +2984,10 @@ read_frames:
     }
 
 finish:
+
+    if (print_graphs || print_graphs_file)
+        print_filtergraph(fg, fgt.graph);
+
     // EOF is normal termination
     if (ret == AVERROR_EOF)
         ret = 0;
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 6ec325f51e..3d1efe32f9 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -47,6 +47,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/stereo3d.h"
+#include "graph/graphprint.h"
 
 HWDevice *filter_hw_device;
 
@@ -75,6 +76,9 @@ float max_error_rate  = 2.0/3;
 char *filter_nbthreads;
 int filter_complex_nbthreads = 0;
 int vstats_version = 2;
+int print_graphs = 0;
+char *print_graphs_file = NULL;
+char *print_graphs_format = NULL;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1735,6 +1739,15 @@ const OptionDef options[] = {
         { .func_arg = opt_filter_complex_script },
         "deprecated, use -/filter_complex instead", "filename" },
 #endif
+    { "print_graphs",   OPT_TYPE_BOOL, 0,
+        { &print_graphs },
+        "print execution graph data to stderr" },
+    { "print_graphs_file", OPT_TYPE_STRING, 0,
+        { &print_graphs_file },
+        "write execution graph data to the specified file", "filename" },
+    { "print_graphs_format", OPT_TYPE_STRING, 0,
+        { &print_graphs_format },
+      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
new file mode 100644
index 0000000000..89c38d2e36
--- /dev/null
+++ b/fftools/graph/graphprint.c
@@ -0,0 +1,1102 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include <string.h>
+#include <stdatomic.h>
+
+#include "graphprint.h"
+
+#include <libavformat/url.h>
+
+#include "fftools/ffmpeg_filter.h"
+#include "fftools/ffmpeg_mux.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+#include "fftools/textformat/avtextformat.h"
+#include "fftools/textformat/tf_mermaid.h"
+#include "fftools/resources/resman.h"
+
+typedef enum {
+    SECTION_ID_ROOT,
+    SECTION_ID_FILTERGRAPHS,
+    SECTION_ID_FILTERGRAPH,
+    SECTION_ID_GRAPH_INPUTS,
+    SECTION_ID_GRAPH_INPUT,
+    SECTION_ID_GRAPH_OUTPUTS,
+    SECTION_ID_GRAPH_OUTPUT,
+    SECTION_ID_FILTERS,
+    SECTION_ID_FILTER,
+    SECTION_ID_FILTER_INPUTS,
+    SECTION_ID_FILTER_INPUT,
+    SECTION_ID_FILTER_OUTPUTS,
+    SECTION_ID_FILTER_OUTPUT,
+    SECTION_ID_HWFRAMESCONTEXT,
+    SECTION_ID_INPUTFILES,
+    SECTION_ID_INPUTFILE,
+    SECTION_ID_INPUTSTREAMS,
+    SECTION_ID_INPUTSTREAM,
+    SECTION_ID_OUTPUTFILES,
+    SECTION_ID_OUTPUTFILE,
+    SECTION_ID_OUTPUTSTREAMS,
+    SECTION_ID_OUTPUTSTREAM,
+    SECTION_ID_STREAMLINKS,
+    SECTION_ID_STREAMLINK,
+    SECTION_ID_DECODERS,
+    SECTION_ID_DECODER,
+    SECTION_ID_ENCODERS,
+    SECTION_ID_ENCODER,
+} SectionID;
+
+static struct AVTextFormatSection sections[] = {
+    [SECTION_ID_ROOT]            = { SECTION_ID_ROOT, "root", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER, { SECTION_ID_FILTERGRAPHS, SECTION_ID_INPUTFILES, SECTION_ID_OUTPUTFILES, SECTION_ID_DECODERS, SECTION_ID_ENCODERS, SECTION_ID_STREAMLINKS, -1 } },
+
+    [SECTION_ID_FILTERGRAPHS]    = { SECTION_ID_FILTERGRAPHS, "graphs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
+    [SECTION_ID_FILTERGRAPH]     = { SECTION_ID_FILTERGRAPH, "graph", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { SECTION_ID_GRAPH_INPUTS, SECTION_ID_GRAPH_OUTPUTS, SECTION_ID_FILTERS, -1 }, .element_name = "graph_info" },
+
+    [SECTION_ID_GRAPH_INPUTS]    = { SECTION_ID_GRAPH_INPUTS, "graph_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_INPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_INPUT]     = { SECTION_ID_GRAPH_INPUT, "graph_input", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_GRAPH_OUTPUTS]   = { SECTION_ID_GRAPH_OUTPUTS, "graph_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_OUTPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_OUTPUT]    = { SECTION_ID_GRAPH_OUTPUT, "graph_output", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTERS]         = { SECTION_ID_FILTERS, "filters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_FILTER, -1 }, .id_key = "graph_id" },
+    [SECTION_ID_FILTER]          = { SECTION_ID_FILTER, "filter", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { SECTION_ID_FILTER_INPUTS, SECTION_ID_FILTER_OUTPUTS, -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_INPUTS]   = { SECTION_ID_FILTER_INPUTS, "filter_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_INPUT, -1 } },
+    [SECTION_ID_FILTER_INPUT]    = { SECTION_ID_FILTER_INPUT, "filter_input", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "source_filter_id", .dest_id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_OUTPUTS]  = { SECTION_ID_FILTER_OUTPUTS, "filter_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_OUTPUT, -1 } },
+    [SECTION_ID_FILTER_OUTPUT]   = { SECTION_ID_FILTER_OUTPUT, "filter_output", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "filter_id", .dest_id_key = "dest_filter_id" },
+
+    [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context",  0, { -1 }, },
+
+    [SECTION_ID_INPUTFILES]      = { SECTION_ID_INPUTFILES, "inputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTFILE]       = { SECTION_ID_INPUTFILE, "inputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_INPUTSTREAMS]    = { SECTION_ID_INPUTSTREAMS, "inputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTSTREAM]     = { SECTION_ID_INPUTSTREAM, "inputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTFILES]     = { SECTION_ID_OUTPUTFILES, "outputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTFILE]      = { SECTION_ID_OUTPUTFILE, "outputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTSTREAMS]   = { SECTION_ID_OUTPUTSTREAMS, "outputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTSTREAM]    = { SECTION_ID_OUTPUTSTREAM, "outputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id", },
+
+    [SECTION_ID_STREAMLINKS]     = { SECTION_ID_STREAMLINKS, "streamlinks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAMLINK, -1 } },
+    [SECTION_ID_STREAMLINK]      = { SECTION_ID_STREAMLINK, "streamlink", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .src_id_key = "source_stream_id", .dest_id_key = "dest_stream_id" },
+
+    [SECTION_ID_DECODERS]        = { SECTION_ID_DECODERS, "decoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_DECODER, -1 } },
+    [SECTION_ID_DECODER]         = { SECTION_ID_DECODER, "decoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "source_id", .dest_id_key = "id" },
+
+    [SECTION_ID_ENCODERS]        = { SECTION_ID_ENCODERS, "encoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_ENCODER, -1 } },
+    [SECTION_ID_ENCODER]         = { SECTION_ID_ENCODER, "encoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "id", .dest_id_key = "dest_id" },
+};
+
+typedef struct GraphPrintContext {
+    AVTextFormatContext *tfc;
+    AVTextWriterContext *wctx;
+    AVDiagramConfig diagram_config;
+
+    int id_prefix_num;
+    int is_diagram;
+    int opt_flags;
+    int skip_buffer_filters;
+    AVBPrint pbuf;
+
+} GraphPrintContext;
+
+/* Text Format API Shortcuts */
+#define print_id(k, v)          print_sanizied_id(gpc, k, v, 0)
+#define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
+#define print_int(k, v)         avtext_print_integer(tfc, k, v)
+#define print_int_opt(k, v)     avtext_print_integer_flags(tfc, k, v, gpc->opt_flags)
+#define print_q(k, v, s)        avtext_print_rational(tfc, k, v, s)
+#define print_str(k, v)         avtext_print_string(tfc, k, v, 0)
+#define print_str_opt(k, v)     avtext_print_string(tfc, k, v, gpc->opt_flags)
+#define print_val(k, v, u)      avtext_print_unit_int(tfc, k, v, u)
+
+#define print_fmt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, 0);    \
+} while (0)
+
+#define print_fmt_opt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, gpc->opt_flags);    \
+} while (0)
+
+
+static atomic_int prefix_num = 0;
+
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    unsigned i;
+    for (i = 0; src[i] && i < dst_size - 1; i++)
+        dst[i]      = (char)av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static char *get_extension(const char *url)
+{
+    const char *ext;
+    URLComponents uc;
+    int ret;
+    char scratchpad[128];
+
+    if (!url)
+        return 0;
+
+    ret = ff_url_decompose(&uc, url, NULL);
+    if (ret < 0)
+        return NULL;
+    for (ext = uc.query; *ext != '.' && ext > uc.path; ext--) {
+    }
+
+    if (*ext != '.')
+        return 0;
+    if (uc.query - ext > sizeof(scratchpad))
+        return NULL; //not enough memory in our scratchpad
+    av_strlcpy(scratchpad, ext + 1, uc.query - ext);
+
+    return av_strdup(scratchpad);
+}
+
+static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+
+    if (!hw_device_context)
+        return;
+
+    print_int_opt("has_hw_device_context", 1);
+    print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
+}
+
+static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    const AVPixFmtDescriptor *pix_desc_hw;
+    const AVPixFmtDescriptor *pix_desc_sw;
+
+    if (!hw_frames_context || !hw_frames_context->device_ctx)
+        return;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT);
+
+    print_int_opt("has_hw_frames_context", 1);
+    print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
+
+    pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
+    if (pix_desc_hw) {
+        print_str("hw_pixel_format", pix_desc_hw->name);
+        if (pix_desc_hw->alias)
+            print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
+    }
+
+    pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
+    if (pix_desc_sw) {
+        print_str("sw_pixel_format", pix_desc_sw->name);
+        if (pix_desc_sw->alias)
+            print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
+    }
+
+    print_int_opt("width", hw_frames_context->width);
+    print_int_opt("height", hw_frames_context->height);
+    print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBufferRef *hw_frames_ctx;
+    char layout_string[64];
+
+    if (!link)
+        return;
+
+    hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
+
+    print_str_opt("media_type", av_get_media_type_string(link->type));
+
+    switch (link->type) {
+    case AVMEDIA_TYPE_VIDEO:
+
+        if (hw_frames_ctx && hw_frames_ctx->data) {
+            AVHWFramesContext *      hwfctx      = (AVHWFramesContext *)hw_frames_ctx->data;
+            const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
+            const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
+            if (pix_desc_hw && pix_desc_sw)
+                print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
+        } else {
+            print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
+        }
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        print_q("sar", link->sample_aspect_ratio, ':');
+
+        if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
+            print_str_opt("color_range", av_color_range_name(link->color_range));
+
+        if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
+            print_str("color_space", av_color_space_name(link->colorspace));
+        break;
+
+    case AVMEDIA_TYPE_SUBTITLE:
+        ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        break;
+
+    case AVMEDIA_TYPE_AUDIO:
+        av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
+        print_str("channel_layout", layout_string);
+        print_val("channels", link->ch_layout.nb_channels, "ch");
+        if (tfc->show_value_unit)
+            print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
+        else
+            print_val("sample_rate", link->sample_rate, "Hz");
+
+        break;
+    }
+
+    print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
+
+    if (hw_frames_ctx && hw_frames_ctx->data)
+        print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
+}
+
+static char sanitize_char(const char c)
+{
+    if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+        return c;
+    return '_';
+}
+
+static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBPrint buf;
+
+    if (!key || !id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    print_str(key, buf.str);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+    AVBPrint buf;
+
+    if (!id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    sec_ctx.context_id = buf.str;
+
+    avtext_print_section_header(tfc, &sec_ctx, section_id);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static const char *get_filterpad_name(const AVFilterPad *pad)
+{
+    return pad ? avfilter_pad_get_name(pad, 0) : "pad";
+}
+
+static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
+
+    ////print_id("filter_id", filter->name);
+
+    if (filter->filter) {
+        print_str("filter_name", filter->filter->name);
+        print_str_opt("description", filter->filter->description);
+        print_int_opt("nb_inputs", filter->nb_inputs);
+        print_int_opt("nb_outputs", filter->nb_outputs);
+    }
+
+    if (filter->hw_device_ctx) {
+        AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
+        print_hwdevicecontext(gpc, device_context);
+        if (filter->extra_hw_frames > 0)
+            print_int("extra_hw_frames", filter->extra_hw_frames);
+    }
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
+
+    for (unsigned i = 0; i < filter->nb_inputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->inputs[i];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
+        sec_ctx.context_type = NULL;
+
+        print_int_opt("input_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
+
+        dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
+        if (dic_entry) {
+            char buf[256];
+            (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
+            print_id_noprefix("source_filter_id", buf);
+        } else {
+            print_id("source_filter_id", link->src->name);
+        }
+
+        print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
+
+    for (unsigned i = 0; i < filter->nb_outputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->outputs[i];
+        char buf[256];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
+        sec_ctx.context_type = NULL;
+
+        dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
+        if (dic_entry) {
+            (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
+            print_id_noprefix("dest_filter_id", buf);
+        } else {
+            print_id("dest_filter_id", link->dst->name);
+        }
+
+        print_int_opt("output_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->srcpad));
+        ////print_id("dest_filter_id", link->dst->name);
+        print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER
+}
+
+static void init_sections(void)
+{
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
+        sections[i].show_all_entries = 1;
+}
+
+static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVDictionary *input_map = NULL;
+    AVDictionary *output_map = NULL;
+
+    print_int("graph_index", fg->index);
+    print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+    print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
+    print_str("description", fgp->graph_desc);
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
+
+    for (int i = 0; i < fg->nb_inputs; i++) {
+        InputFilterPriv *ifilter = ifp_from_ifilter(fg->inputs[i]);
+        enum AVMediaType media_type = ifilter->type;
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
+
+        print_int("input_index", ifilter->index);
+
+        if (ifilter->linklabel)
+            print_str("link_label", (const char*)ifilter->linklabel);
+
+        if (ifilter->filter) {
+            print_id("filter_id", ifilter->filter->name);
+            print_str("filter_name", ifilter->filter->filter->name);
+        }
+
+        if (ifilter->linklabel && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
+        else if (ifilter->opts.name && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->opts.name, 0);
+
+        print_str("media_type", av_get_media_type_string(media_type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
+
+    for (int i = 0; i < fg->nb_outputs; i++) {
+        OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
+
+        print_int("output_index", ofilter->index);
+
+        print_str("name", ofilter->name);
+
+        if (fg->outputs[i]->linklabel)
+            print_str("link_label", (const char*)fg->outputs[i]->linklabel);
+
+        if (ofilter->filter) {
+            print_id("filter_id", ofilter->filter->name);
+            print_str("filter_name", ofilter->filter->filter->name);
+        }
+
+        if (ofilter->name && ofilter->filter)
+            av_dict_set(&output_map, ofilter->filter->name, ofilter->name, 0);
+
+
+        print_str("media_type", av_get_media_type_string(fg->outputs[i]->type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
+
+    if (graph) {
+        AVTextFormatSectionContext sec_ctx = { 0 };
+
+        sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTERS);
+
+        if (gpc->is_diagram) {
+            print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+            print_str("description", fgp->graph_desc);
+            print_str("id", sec_ctx.context_id);
+        }
+
+        av_freep(&sec_ctx.context_id);
+
+        for (unsigned i = 0; i < graph->nb_filters; i++) {
+            AVFilterContext *filter = graph->filters[i];
+
+            if (gpc->skip_buffer_filters) {
+                if (av_dict_get(input_map, filter->name, NULL, 0))
+                    continue;
+                if (av_dict_get(output_map, filter->name, NULL, 0))
+                    continue;
+            }
+
+            sec_ctx.context_id = filter->name;
+
+            print_filter(gpc, filter, input_map, output_map);
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
+    }
+
+    // Clean up dictionaries
+    av_dict_free(&input_map);
+    av_dict_free(&output_map);
+}
+
+static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    AVTextFormatContext       *tfc = gpc->tfc;
+    AVBPrint                   buf;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    sec_ctx.context_id = "Inputs";
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+    print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
+
+    for (int n = nb_ifiles - 1; n >= 0; n--) {
+        InputFile *ifi = ifiles[n];
+        AVFormatContext *fc = ifi->ctx;
+
+        sec_ctx.context_id = av_asprintf("Input_%d", n);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
+        av_freep(&sec_ctx.context_id);
+
+        print_fmt("index", "%d", ifi->index);
+
+        if (fc) {
+            print_str("demuxer_name", fc->iformat->name);
+            if (fc->url) {
+                char *extension = get_extension(fc->url);
+                if (extension) {
+                    print_str("file_extension", extension);
+                    av_freep(&extension);
+                }
+                print_str("url", fc->url);
+            }
+        }
+
+        sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
+
+        av_freep(&sec_ctx.context_id);
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+            const AVCodecDescriptor *codec_desc;
+
+            if (!ist || !ist->par)
+                continue;
+
+            codec_desc = avcodec_descriptor_get(ist->par->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
+
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_in_%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else if (ist->dec) {
+                char char_buf[256];
+                av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
+                av_bprintf(&buf, "%s", "Attachment");
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
+                av_bprintf(&buf, "%s", "Data");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ist->index);
+
+            if (ist->dec)
+                print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
+
+
+    print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
+
+    for (int n = 0; n < nb_ifiles; n++) {
+        InputFile *ifi = ifiles[n];
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+
+            if (!ist->decoder)
+                continue;
+
+            sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_DECODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("source_id", "r_in_%d_%d", n, i);
+            print_fmt("id", "in_%d_%d", n, i);
+
+            ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            print_fmt("name", "%s", ist->dec->name);
+
+            print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_DECODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            ////const AVCodecDescriptor *codec_desc;
+
+            if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
+                continue;
+
+            ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_ENCODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "out__%d_%d", n, i);
+            print_fmt("dest_id", "r_out__%d_%d", n, i);
+
+            print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
+
+    for (int n = nb_ofiles - 1; n >= 0; n--) {
+        OutputFile *of = ofiles[n];
+        Muxer *muxer = (Muxer *)of;
+
+        if (!muxer->fc)
+            continue;
+
+        sec_ctx.context_id = av_asprintf("Output_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
+
+        av_freep(&sec_ctx.context_id);
+
+        ////print_str_opt("index", av_get_media_type_string(of->index));
+        print_fmt("index", "%d", of->index);
+        ////print_str("url", of->url);
+        print_str("muxer_name", muxer->fc->oformat->name);
+        if (of->url) {
+            char *extension = get_extension(of->url);
+            if (extension) {
+                print_str("file_extension", extension);
+                av_freep(&extension);
+            }
+            print_str("url", of->url);
+        }
+
+        sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_out__%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else {
+                av_bprintf(&buf, "%s", "unknown");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ost->index);
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
+
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->ist && !ost->filter) {
+                sec_ctx.context_type = av_get_media_type_string(ost->type);
+                avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_STREAMLINK);
+                sec_ctx.context_type = NULL;
+
+                if (ost->enc) {
+                    print_fmt("dest_stream_id", "out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Transcode");
+                } else {
+                    print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Stream Copy");
+                }
+
+                print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+                avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
+
+    return 0;
+}
+
+
+static void uninit_graphprint(GraphPrintContext *gpc)
+{
+    if (gpc->tfc)
+        avtext_context_close(&gpc->tfc);
+
+    if (gpc->wctx)
+        avtextwriter_context_close(&gpc->wctx);
+
+    // Finalize the print buffer if it was initialized
+    av_bprint_finalize(&gpc->pbuf, NULL);
+}
+
+static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
+{
+    const AVTextFormatter *text_formatter;
+    AVTextFormatContext *tfc = NULL;
+    AVTextWriterContext *wctx = NULL;
+    GraphPrintContext *gpc = NULL;
+    char *w_args = NULL;
+    char *w_name;
+    int ret;
+
+    init_sections();
+    *pgpc = NULL;
+
+    av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("json");
+    if (!print_graphs_format) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &w_args);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    text_formatter = avtext_get_formatter_by_name(w_name);
+    if (!text_formatter) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtextwriter_create_buffer(&wctx, target_buf);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), 0, 0, 0, 0, -1, NULL);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    gpc = av_mallocz(sizeof(GraphPrintContext));
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    gpc->wctx = wctx;
+    gpc->tfc = tfc;
+    av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
+    gpc->is_diagram = !!(tfc->formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
+    if (gpc->is_diagram) {
+        tfc->show_value_unit = 1;
+        tfc->show_optional_fields = -1;
+        gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+        gpc->skip_buffer_filters = 1;
+        ////} else {
+        ////    gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+    }
+
+    if (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml")) {
+        gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
+
+        if (!strcmp(text_formatter->name, "mermaidhtml"))
+            gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
+
+        av_diagram_init(tfc, &gpc->diagram_config);
+    }
+
+    *pgpc = gpc;
+
+    return 0;
+
+fail:
+    if (tfc)
+        avtext_context_close(&tfc);
+    if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
+        avtextwriter_context_close(&wctx);
+    av_freep(&gpc);
+
+    return ret;
+}
+
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVBPrint *target_buf = &fgp->graph_print_buf;
+    int ret;
+
+    if (!fg || !fgp) {
+        av_log(NULL, AV_LOG_ERROR, "Invalid filter graph provided\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (target_buf->len)
+        av_bprint_finalize(target_buf, NULL);
+
+    ret = init_graphprint(&gpc, target_buf);
+    if (ret)
+        return ret;
+
+    if (!gpc) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to initialize graph print context\n");
+        return AVERROR(ENOMEM);
+    }
+
+    tfc = gpc->tfc;
+
+    // Due to the threading model each graph needs to print itself into a buffer
+    // from its own thread. The actual printing happens short before cleanup in ffmpeg.c
+    // where all graphs are assembled together. To make this work, we need to put the
+    // formatting context into the same state like it would be when printing all at once,
+    // so here we print the section headers and clear the buffer to get into the right state.
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+
+    av_bprint_clear(target_buf);
+
+    print_filtergraph_single(gpc, fg, graph);
+
+    if (gpc->is_diagram) {
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+    }
+
+    uninit_graphprint(gpc);
+
+    return 0;
+}
+
+static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    AVBPrint target_buf;
+    int ret;
+
+    ret = init_graphprint(&gpc, &target_buf);
+    if (ret)
+        goto cleanup;
+
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto cleanup;
+    }
+
+    tfc = gpc->tfc;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+
+    for (int i = 0; i < nb_graphs; i++) {
+        FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
+        AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+        if (graph_buf->len > 0) {
+            avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+            av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+            av_bprint_finalize(graph_buf, NULL);
+            avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        }
+    }
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->fg_simple) {
+                FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
+                AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+                if (graph_buf->len > 0) {
+                    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+                    av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+                    av_bprint_finalize(graph_buf, NULL);
+                    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+                }
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+
+    print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ROOT
+
+    if (print_graphs_file) {
+        AVIOContext *avio = NULL;
+
+        if (!strcmp(print_graphs_file, "-")) {
+            printf("%s", target_buf.str);
+        } else {
+            ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
+                goto cleanup;
+            }
+
+            avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
+            avio_flush(avio);
+
+            if ((ret = avio_closep(&avio)) < 0)
+                av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
+        }
+    }
+
+    if (print_graphs)
+        av_log(NULL, AV_LOG_INFO, "%s    %c", target_buf.str, '\n');
+
+cleanup:
+    // Properly clean up resources
+    if (gpc)
+        uninit_graphprint(gpc);
+
+    // Ensure the target buffer is properly finalized
+    av_bprint_finalize(&target_buf, NULL);
+
+    return ret;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+}
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
new file mode 100644
index 0000000000..9f043cc273
--- /dev/null
+++ b/fftools/graph/graphprint.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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_GRAPH_GRAPHPRINT_H
+#define FFTOOLS_GRAPH_GRAPHPRINT_H
+
+#include "fftools/ffmpeg.h"
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles);
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index b862b70d9f..4cda64a14f 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -716,6 +716,8 @@ static void formatters_register_all(void)
     registered_formatters[4] = &avtextformatter_ini;
     registered_formatters[5] = &avtextformatter_json;
     registered_formatters[6] = &avtextformatter_xml;
+    registered_formatters[7] = &avtextformatter_mermaid;
+    registered_formatters[8] = &avtextformatter_mermaidhtml;
 }
 
 const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index 391ecdb624..3090656020 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -31,6 +31,12 @@
 
 #define SECTION_MAX_NB_CHILDREN 11
 
+typedef struct AVTextFormatSectionContext {
+    char *context_id;
+    const char *context_type;
+    int context_flags;
+} AVTextFormatSectionContext;
+
 
 typedef struct AVTextFormatSection {
     int id;             ///< unique id identifying a section
@@ -42,6 +48,10 @@ typedef struct AVTextFormatSection {
                                            ///  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
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE       32 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS      64 ///< ...
+#define AV_TEXTFORMAT_SECTION_PRINT_TAGS         128 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH   256 ///< ...
 
     int flags;
     const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
@@ -50,12 +60,17 @@ typedef struct AVTextFormatSection {
     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;
+    const char *id_key;          ///< name of the key to be used as the id 
+    const char *src_id_key;     ///< name of the key to be used as the source id for diagram connections
+    const char *dest_id_key;   ///< name of the key to be used as the target id for diagram connections
+    const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
 } AVTextFormatSection;
 
 typedef struct AVTextFormatContext AVTextFormatContext;
 
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
+#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER         4
 
 typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
@@ -64,6 +79,18 @@ typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_NB
 } StringValidation;
 
+typedef enum {
+    AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_NONDIR,
+    AV_TEXTFORMAT_LINKTYPE_HIDDEN,
+    AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
+} AVTextFormatLinkType;
+
 typedef struct AVTextFormatter {
     const AVClass *priv_class;      ///< private class of the formatter, if any
     int priv_size;                  ///< private size for the formatter context
@@ -167,5 +194,7 @@ extern const AVTextFormatter avtextformatter_flat;
 extern const AVTextFormatter avtextformatter_ini;
 extern const AVTextFormatter avtextformatter_json;
 extern const AVTextFormatter avtextformatter_xml;
+extern const AVTextFormatter avtextformatter_mermaid;
+extern const AVTextFormatter avtextformatter_mermaidhtml;
 
 #endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
diff --git a/fftools/textformat/tf_mermaid.c b/fftools/textformat/tf_mermaid.c
new file mode 100644
index 0000000000..e0ea89b02a
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.c
@@ -0,0 +1,655 @@
+/*
+ * 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 "tf_internal.h"
+#include "tf_mermaid.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+
+static const char *init_directive = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 10,"
+        "\"nodeSpacing\": 10,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"flowchart\": { "
+            "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
+            "\"diagramPadding\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char* init_directive_er = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"layout\": \"elk\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 65,"
+        "\"nodeSpacing\": 60,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"er\": { "
+            "\"diagramPadding\": 12, "
+            "\"entityPadding\": 4, "
+            "\"minEntityWidth\": 150, "
+            "\"minEntityHeight\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char *theme_css_er = ""
+
+    // Variables
+            ".root { "
+                "--ff-colvideo: #6eaa7b; "
+                "--ff-colaudio: #477fb3; "
+                "--ff-colsubtitle: #ad76ab; "
+                "--ff-coltext: #666; "
+            "} "
+            " g.nodes g.node.default rect.basic.label-container, "
+            " g.nodes g.node.default path { "
+            "     rx: 1; "
+            "     ry: 1; "
+            "     stroke-width: 1px !important; "
+            "     stroke: #e9e9e9 !important; "
+            "     fill: url(#ff-filtergradient) !important; "
+            "     filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
+            "     fill: white !important; "
+            " } "
+            "  "
+            " .relationshipLine { "
+            "     stroke: gray; "
+            "     stroke-width: 1; "
+            "     fill: none; "
+            "     filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
+            " } "
+            "  "
+            " g.node.default g.label.name  foreignObject > div > span > p, "
+            " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
+            "     font-size: 0.95rem; "
+            "     font-weight: 500; "
+            "     text-transform: uppercase; "
+            "     min-width: 5.5rem; "
+            "     margin-bottom: 0.5rem; "
+            "      "
+            " } "
+            "  "
+            " .edgePaths path { "
+            "     marker-end: none; "
+            "     marker-start: none; "
+            "  "
+            "} ";
+
+
+/* Mermaid Graph output */
+
+typedef struct MermaidContext {
+    const AVClass *class;
+    AVDiagramConfig *diagram_config;
+    int subgraph_count;
+    int within_tag;
+    int indent_level;
+    int create_html;
+
+    // Options
+    int enable_link_colors; // Requires Mermaid 11.5
+
+    struct section_data {
+        const char *section_id;
+        const char *section_type;
+        const char *src_id;
+        const char *dest_id;
+        AVTextFormatLinkType link_type;
+        int current_is_textblock;
+        int current_is_stadium;
+        int subgraph_start_incomplete;
+    }  section_data[SECTION_MAX_NB_LEVELS];
+
+    unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint link_buf; ///< print buffer for writing diagram links
+    AVDictionary *link_dict;
+} MermaidContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(MermaidContext, x)
+
+static const AVOption mermaid_options[] = {
+    { "link_coloring",    "enable colored links (requires Mermaid >= 11.5)",  OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL,   { .i64 = 1 },  0, 1 },
+    ////{"diagram_css",      "CSS for the diagram",                              OFFSET(diagram_css),        AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    ////{"html_template",    "Template HTML",                                    OFFSET(html_template),      AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    { NULL },
+};
+
+DEFINE_FORMATTER_CLASS(mermaid);
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
+{
+    MermaidContext *mmc = tfc->priv;
+    mmc->diagram_config = diagram_config;
+}
+
+static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
+{
+    MermaidContext *mmc = tfc->priv;
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&buf, "%s--%s", src, dest);
+
+    if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
+        return 1;
+
+    av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
+
+    return 0;
+}
+
+static av_cold int mermaid_init(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
+    return 0;
+}
+
+static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    int ret = mermaid_init(tfc);
+
+    if (ret < 0)
+        return ret;
+
+    mmc->create_html = 1;
+
+    return 0;
+}
+
+#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
+
+static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
+{
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSectionContext *sec_ctx = data;
+
+    if (tfc->level == 0) {
+        char *directive;
+        AVBPrint css_buf;
+        const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
+        char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
+        ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
+        av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+        av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
+        av_freep(&single_line_css);
+
+        directive = av_strireplace(diag_directive, "__###__", css_buf.str);
+        if (mmc->create_html) {
+            uint64_t length;
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+
+            length = token_pos - mmc->diagram_config->html_template;
+            for (uint64_t i = 0; i < length; i++)
+                writer_w8(tfc, mmc->diagram_config->html_template[i]);
+        }
+
+        writer_put_str(tfc, directive);
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+            writer_put_str(tfc, "flowchart LR\n");
+        ////writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
+            writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%);     \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%);   \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%);     \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            writer_put_str(tfc, "erDiagram\n");
+            break;
+        }
+
+        return;
+    }
+
+    if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
+        AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
+
+        if (parent_sec_data.subgraph_start_incomplete) {
+
+            if (parent_buf->len > 0)
+                writer_printf(tfc, "%s", parent_buf->str);
+
+            writer_put_str(tfc, "</div>\"]\n");
+
+            mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
+        }
+    }
+
+    av_freep(&mmc->section_data[tfc->level].section_id);
+    av_freep(&mmc->section_data[tfc->level].section_type);
+    av_freep(&mmc->section_data[tfc->level].src_id);
+    av_freep(&mmc->section_data[tfc->level].dest_id);
+    mmc->section_data[tfc->level].current_is_textblock = 0;
+    mmc->section_data[tfc->level].current_is_stadium = 0;
+    mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
+    mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
+
+    // NOTE: av_strdup() allocations aren't checked
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+            MM_INDENT();
+            writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+
+            mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+                if (sec_ctx->context_flags & 1) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_textblock = 1;
+                } else if (sec_ctx->context_flags & 2) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s([\"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_stadium = 1;
+                } else {
+                    MM_INDENT();
+                    writer_printf(tfc, "%s(\"", sec_ctx->context_id);
+                }
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+                MM_INDENT();
+                writer_printf(tfc, "%s {\n", sec_ctx->context_id);
+                break;
+            }
+
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
+
+        if (sec_ctx && sec_ctx->context_type)
+            writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
+        else
+            writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        av_bprint_clear(buf);
+        mmc->nb_link_captions[tfc->level] = 0;
+
+        if (sec_ctx && sec_ctx->context_type)
+            mmc->section_data[tfc->level].section_type = av_strdup(sec_ctx->context_type);
+
+        ////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(tfc, " type=\"%s\"", buf.str);
+    }
+}
+
+static void mermaid_print_section_footer(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
+        writer_put_str(tfc, "</div>");
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (sec_data.current_is_textblock) {
+                writer_printf(tfc, "\"}\n", section->name);
+
+                if (sec_data.section_id) {
+                    MM_INDENT();
+                    writer_put_str(tfc, "class ");
+                    writer_put_str(tfc, sec_data.section_id);
+                    writer_put_str(tfc, " ff-");
+                    writer_put_str(tfc, section->name);
+                    writer_put_str(tfc, "\n");
+                }
+            } else if (sec_data.current_is_stadium) {
+                writer_printf(tfc, "\"]):::ff-%s\n", section->name);
+            } else {
+                writer_printf(tfc, "\"):::ff-%s\n", section->name);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            MM_INDENT();
+            writer_put_str(tfc, "}\n\n");
+            break;
+        }
+
+        mmc->indent_level--;
+
+    } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
+
+        MM_INDENT();
+        writer_put_str(tfc, "end\n");
+
+        if (sec_data.section_id) {
+            MM_INDENT();
+            writer_put_str(tfc, "class ");
+            writer_put_str(tfc, sec_data.section_id);
+            writer_put_str(tfc, " ff-");
+            writer_put_str(tfc, section->name);
+            writer_put_str(tfc, "\n");
+        }
+
+        mmc->indent_level--;
+    }
+
+    if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
+        if (sec_data.src_id && sec_data.dest_id
+            && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+
+                if (sec_data.section_type && mmc->enable_link_colors)
+                    av_bprintf(&mmc->link_buf, "\n  %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
+                else
+                    av_bprintf(&mmc->link_buf, "\n  %s ==", sec_data.src_id);
+
+                if (buf->len > 0) {
+                    av_bprintf(&mmc->link_buf, " \"%s", buf->str);
+
+                    for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
+                        av_bprintf(&mmc->link_buf, "<br>&nbsp;");
+
+                    av_bprintf(&mmc->link_buf, "\" ==");
+                }
+
+                av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+
+                av_bprintf(&mmc->link_buf, "\n  %s", sec_data.src_id);
+
+                switch (sec_data.link_type) {
+                case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
+                    break;
+                default:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                }
+
+                av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
+
+                break;
+            }
+
+    if (tfc->level == 0) {
+
+        writer_put_str(tfc, "\n");
+        if (mmc->create_html) {
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+            token_pos += strlen("__###__");
+            writer_put_str(tfc, token_pos);
+        }
+    }
+
+    if (tfc->level == 1) {
+
+        if (mmc->link_buf.len > 0) {
+            writer_put_str(tfc, mmc->link_buf.str);
+            av_bprint_clear(&mmc->link_buf);
+        }
+
+        writer_put_str(tfc, "\n");
+    }
+}
+
+static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
+                                const char *str, int64_t num, const int is_int)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+    int exit = 0;
+
+    if (section->id_key && !strcmp(section->id_key, key)) {
+        mmc->section_data[tfc->level].section_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
+        mmc->section_data[tfc->level].dest_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->src_id_key && !strcmp(section->src_id_key, key)) {
+        mmc->section_data[tfc->level].src_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->linktype_key && !strcmp(section->linktype_key, key)) {
+        mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
+        exit = 1;
+    }
+
+    //if (exit)
+    //    return;
+
+    if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
+        || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
+
+        if (exit)
+            return;
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (is_int) {
+                writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
+            } else {
+                ////AVBPrint b;
+                ////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
+                const char *tmp = av_strireplace(str, "\"", "'");
+                ////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
+                writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
+                av_freep(&tmp);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            {
+                char *col_type;
+
+                if (key[0] == '_')
+                    return;
+
+                if (sec_data.section_id && !strcmp(str, sec_data.section_id))
+                    col_type = "PK";
+                else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
+                    col_type = "FK";
+                else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
+                    col_type = "FK";
+                else
+                    col_type = "";
+
+                MM_INDENT();
+
+                if (is_int)
+                    writer_printf(tfc, "    %s %"PRId64" %s\n", key, num, col_type);
+                else
+                    writer_printf(tfc, "    %s %s %s\n", key, str, col_type);
+            }
+            break;
+        }
+
+    } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        if (exit)
+            return;
+
+        if (buf->len > 0)
+            av_bprintf(buf, "%s", "<br>");
+
+        av_bprintf(buf, "");
+        if (is_int)
+            av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
+        else
+            av_bprintf(buf, "<span>%s</span>", str);
+
+        mmc->nb_link_captions[tfc->level]++;
+    }
+}
+
+static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
+{
+    mermaid_print_value(tfc, key, value, 0, 0);
+}
+
+static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
+{
+    mermaid_print_value(tfc, key, NULL, value, 1);
+}
+
+const AVTextFormatter avtextformatter_mermaid = {
+    .name                 = "mermaid",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
+
+
+const AVTextFormatter avtextformatter_mermaidhtml = {
+    .name                 = "mermaidhtml",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init_html,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
diff --git a/fftools/textformat/tf_mermaid.h b/fftools/textformat/tf_mermaid.h
new file mode 100644
index 0000000000..aff73bf9f3
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.h
@@ -0,0 +1,41 @@
+/*
+ * 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_TF_MERMAID_H
+#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
+
+typedef enum {
+    AV_DIAGRAMTYPE_GRAPH,
+    AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
+} AVDiagramType;
+
+typedef struct AVDiagramConfig {
+    AVDiagramType diagram_type;
+    const char *diagram_css;
+    const char *html_template;
+} AVDiagramConfig;
+
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
+
+void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
+
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */
\ No newline at end of file
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH 9/9] fftools/graphprint: Now, make it a Killer-Feature!
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
                   ` (7 preceding siblings ...)
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 8/9] fftools/graphprint: Add execution graph printing softworkz
@ 2025-04-14 12:47 ` softworkz
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
  9 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-14 12:47 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

remember this: -sg   <= show-graph

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi              |   4 +
 fftools/Makefile             |   1 +
 fftools/ffmpeg.c             |   2 +-
 fftools/ffmpeg.h             |   1 +
 fftools/ffmpeg_filter.c      |   2 +-
 fftools/ffmpeg_opt.c         |   4 +
 fftools/graph/filelauncher.c | 204 +++++++++++++++++++++++++++++++++++
 fftools/graph/graphprint.c   |  50 ++++++++-
 fftools/graph/graphprint.h   |  32 ++++++
 9 files changed, 295 insertions(+), 5 deletions(-)
 create mode 100644 fftools/graph/filelauncher.c

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 35675b5309..6e9e7aed0e 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1404,6 +1404,10 @@ Writes execution graph details to the specified file in the format set via -prin
 Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
 The default format is json.
 
+@item -sg (@emph{global})
+Writes the execution graph to a temporary html file (mermaidhtml format) and 
+tries to launch it in the default browser.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index 8d87ea8255..1d1d8a818d 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -22,6 +22,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
     fftools/graph/graphprint.o        \
+    fftools/graph/filelauncher.o      \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
     fftools/textformat/avtextformat.o \
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index 6766ec209c..9875a1f7fd 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -309,7 +309,7 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
 
     if (do_benchmark) {
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7fbf0ad532..49fea0307d 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -721,6 +721,7 @@ extern int print_graphs;
 extern char *print_graphs_file;
 extern char *print_graphs_format;
 extern int auto_conversion_filters;
+extern int show_graph;
 
 extern const AVIOInterruptCB int_cb;
 
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index b774606562..e82e333b7f 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -2985,7 +2985,7 @@ read_frames:
 
 finish:
 
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraph(fg, fgt.graph);
 
     // EOF is normal termination
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3d1efe32f9..24713d640f 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -79,6 +79,7 @@ int vstats_version = 2;
 int print_graphs = 0;
 char *print_graphs_file = NULL;
 char *print_graphs_format = NULL;
+int show_graph = 0;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1748,6 +1749,9 @@ const OptionDef options[] = {
     { "print_graphs_format", OPT_TYPE_STRING, 0,
         { &print_graphs_format },
       "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
+    { "sg",   OPT_TYPE_BOOL, 0,
+        { &show_graph },
+        "create execution graph as temporary html file and try to launch it in the default browser" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/filelauncher.c b/fftools/graph/filelauncher.c
new file mode 100644
index 0000000000..ae9d88c2e4
--- /dev/null
+++ b/fftools/graph/filelauncher.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(_WIN32)
+#  include <windows.h>
+#else
+#  include <sys/time.h>
+#  include <time.h>
+#endif
+#include "graphprint.h"
+
+int ff_open_html_in_browser(const char *html_path)
+{
+    if (!html_path || !*html_path)
+        return -1;
+
+#if defined(_WIN32)
+
+    // --- Windows ---------------------------------
+    {
+        HINSTANCE rc = ShellExecuteA(NULL, "open", html_path, NULL, NULL, SW_SHOWNORMAL);
+        if ((UINT_PTR)rc <= 32) {
+            // Fallback: system("start ...")
+            char cmd[1024];
+            _snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "start \"\" \"%s\"", html_path);
+            if (system(cmd) != 0)
+                return -1;
+        }
+        return 0;
+    }
+
+#elif defined(__APPLE__)
+
+    // --- macOS -----------------------------------
+    {
+        // "open" is the macOS command to open a file/URL with the default application
+        char cmd[1024];
+        snprintf(cmd, sizeof(cmd), "open '%s' 1>/dev/null 2>&1 &", html_path);
+        if (system(cmd) != 0)
+            return -1;
+        return 0;
+    }
+
+#else
+
+    // --- Linux / Unix-like -----------------------
+    // We'll try xdg-open, then gnome-open, then kfmclient
+    {
+        // Helper macro to try one browser command
+        // Returns 0 on success, -1 on failure
+        #define TRY_CMD(prog) do {                                   \
+            char buf[1024];                                          \
+            snprintf(buf, sizeof(buf), "%s '%s' 1>/dev/null 2>&1 &", \
+                     (prog), html_path);                              \
+            int ret = system(buf);                                    \
+            /* On Unix: system() returns -1 if the shell can't run. */\
+            /* Otherwise, check exit code in lower 8 bits.           */\
+            if (ret != -1 && WIFEXITED(ret) && WEXITSTATUS(ret) == 0) \
+                return 0;                                             \
+        } while (0)
+
+        TRY_CMD("xdg-open");
+        TRY_CMD("gnome-open");
+        TRY_CMD("kfmclient exec");
+
+        fprintf(stderr, "Could not open '%s' in a browser.\n", html_path);
+        return -1;
+    }
+
+#endif
+}
+
+
+int ff_get_temp_dir(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    // --- Windows ------------------------------------
+    {
+        // GetTempPathA returns length of the string (including trailing backslash).
+        // If the return value is greater than buffer size, it's an error.
+        DWORD len = GetTempPathA((DWORD)size, buf);
+        if (len == 0 || len > size) {
+            // Could not retrieve or buffer is too small
+            return -1;
+        }
+        return 0;
+    }
+
+#else
+
+    // --- macOS / Linux / Unix -----------------------
+    // Follow typical POSIX convention: check common env variables
+    // and fallback to /tmp if not found.
+    {
+        const char *tmp = getenv("TMPDIR");
+        if (!tmp || !*tmp) tmp = getenv("TMP");
+        if (!tmp || !*tmp) tmp = getenv("TEMP");
+        if (!tmp || !*tmp) tmp = "/tmp";
+
+        // Copy into buf, ensure there's a trailing slash
+        size_t len = strlen(tmp);
+        if (len + 2 > size) {
+            // Need up to len + 1 for slash + 1 for null terminator
+            return -1;
+        }
+
+        strcpy(buf, tmp);
+        // Append slash if necessary
+        if (buf[len - 1] != '/' && buf[len - 1] != '\\') {
+#if defined(__APPLE__)
+            // On macOS/Unix, use forward slash
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#else
+            // Technically on Unix it's always '/', but here's how you'd do if needed:
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#endif
+        }
+        return 0;
+    }
+
+#endif
+}
+
+int ff_make_timestamped_html_name(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    /*----------- Windows version -----------*/
+    SYSTEMTIME st;
+    GetLocalTime(&st);
+    /*
+      st.wYear, st.wMonth, st.wDay,
+      st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
+    */
+    int written = _snprintf_s(buf, size, _TRUNCATE,
+                              "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                              st.wYear,
+                              st.wMonth,
+                              st.wDay,
+                              st.wHour,
+                              st.wMinute,
+                              st.wSecond,
+                              st.wMilliseconds);
+    if (written < 0)
+        return -1; /* Could not write into buffer */
+    return 0;
+
+#else
+
+    /*----------- macOS / Linux / Unix version -----------*/
+    struct timeval tv;
+    if (gettimeofday(&tv, NULL) != 0) {
+        return -1; /* gettimeofday failed */
+    }
+
+    struct tm local_tm;
+    localtime_r(&tv.tv_sec, &local_tm);
+
+    int ms = (int)(tv.tv_usec / 1000); /* convert microseconds to milliseconds */
+
+    /* 
+       local_tm.tm_year is years since 1900,
+       local_tm.tm_mon  is 0-based (0=Jan, 11=Dec) 
+    */
+    int written = snprintf(buf, size,
+                           "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                           local_tm.tm_year + 1900,
+                           local_tm.tm_mon + 1,
+                           local_tm.tm_mday,
+                           local_tm.tm_hour,
+                           local_tm.tm_min,
+                           local_tm.tm_sec,
+                           ms);
+    if (written < 0 || (size_t)written >= size) {
+        return -1; /* Buffer too small or formatting error */
+    }
+    return 0;
+
+#endif
+}
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
index 89c38d2e36..8241c51e6c 100644
--- a/fftools/graph/graphprint.c
+++ b/fftools/graph/graphprint.c
@@ -586,8 +586,6 @@ static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifil
     AVBPrint                   buf;
     AVTextFormatSectionContext sec_ctx = { 0 };
 
-    sec_ctx.context_id = "Inputs";
-
     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
 
     print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
@@ -875,6 +873,11 @@ static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
 
     av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
 
+    if (show_graph) {
+        if (!print_graphs_format || strcmp(print_graphs_format, "mermaidhtml") != 0)
+            print_graphs_format = av_strdup("mermaidhtml");
+    }
+
     if (!print_graphs_format)
         print_graphs_format = av_strdup("json");
     if (!print_graphs_format) {
@@ -1098,5 +1101,46 @@ cleanup:
 
 int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
 {
-    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+    int ret;
+
+    if (show_graph) {
+        char buf[2048];
+        AVBPrint bp;
+
+        av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+        print_graphs = 0;
+
+        ret = ff_get_temp_dir(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error getting temp directory path for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        ret = ff_make_timestamped_html_name(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error creating temp file name for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        av_bprint_finalize(&bp, &print_graphs_file);
+    }
+
+    ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    if (!ret && show_graph) {
+        av_log(NULL, AV_LOG_INFO, "Execution graph saved as: %s\n", print_graphs_file);
+        av_log(NULL, AV_LOG_INFO, "Trying to launch graph in browser...\n");
+
+        ret = ff_open_html_in_browser(print_graphs_file);
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Browser could not be launched for execution graph display\nPlease open manually: %s\n", print_graphs_file);
+        }
+    }
+
+    return ret;
 }
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
index 9f043cc273..43f769870b 100644
--- a/fftools/graph/graphprint.h
+++ b/fftools/graph/graphprint.h
@@ -27,4 +27,36 @@ int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles,
 
 int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
 
+/**
+ * Open an HTML file in the default browser (Windows, macOS, Linux/Unix).
+ *
+ * @param html_path Absolute or relative path to the HTML file.
+ * @return 0 on success, -1 on failure.
+ *
+ * NOTE: This uses system() calls for non-Windows, and ShellExecute on Windows.
+ *       Exercise caution if 'html_path' is untrusted (possible command injection).
+ */
+int ff_open_html_in_browser(const char *html_path);
+
+/**
+ * Retrieve the system's temporary directory.
+ *
+ * @param buf  Output buffer to store the temp directory path (including trailing slash)
+ * @param size Size of the output buffer in bytes
+ * @return 0 on success, -1 on failure (buffer too small or other errors)
+ *
+ * Note: On most platforms, the path will include a trailing slash (e.g. "C:\\Users\\...\\Temp\\" on Windows, "/tmp/" on Unix).
+ */
+int ff_get_temp_dir(char *buf, size_t size);
+
+/**
+ * Create a timestamped HTML filename, e.g.:
+ *   ffmpeg_graph_2024-01-01_22-12-59_123.html
+ *
+ * @param buf  Pointer to buffer where the result is stored
+ * @param size Size of the buffer in bytes
+ * @return 0 on success, -1 on error (e.g. buffer too small)
+ */
+int ff_make_timestamped_html_name(char *buf, size_t size);
+
 #endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements
  2025-04-14 12:46 ` [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements softworkz
@ 2025-04-15  1:05   ` Andreas Rheinhardt
  2025-04-15  3:19     ` softworkz .
  2025-04-16  9:52     ` softworkz .
  0 siblings, 2 replies; 130+ messages in thread
From: Andreas Rheinhardt @ 2025-04-15  1:05 UTC (permalink / raw)
  To: ffmpeg-devel

softworkz:
> From: softworkz <softworkz@hotmail.com>
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/avtextformat.c | 121 +++++++++++++++++++-----------
>  fftools/textformat/avtextformat.h |   6 +-
>  fftools/textformat/tf_default.c   |   8 +-
>  fftools/textformat/tf_ini.c       |   2 +-
>  fftools/textformat/tf_json.c      |   8 +-
>  fftools/textformat/tf_xml.c       |   3 -
>  fftools/textformat/tw_avio.c      |   9 ++-
>  7 files changed, 101 insertions(+), 56 deletions(-)
> 
> diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
> index 1ce51d11e2..406025d19d 100644
> --- a/fftools/textformat/avtextformat.c
> +++ b/fftools/textformat/avtextformat.c
> @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
>  
>  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++)
> +    for (unsigned i = 0; i < ubuf_size; i++)

Why not size_t?

>          av_bprintf(bp, "%02X", ubuf[i]);
>  }
>  
> @@ -110,8 +109,6 @@ int avtext_context_close(AVTextFormatContext **ptctx)
>  
>      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++)
> @@ -141,12 +138,18 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
>      AVTextFormatContext *tctx;
>      int i, ret = 0;
>  
> -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> +    if (!ptctx || !formatter)
> +        return AVERROR(EINVAL);

Can this happen?

> +
> +    if (!formatter->priv_size && formatter->priv_class)
> +        return AVERROR(EINVAL);

Stuff like this should never happen and should not be checked (or
actually: the proper place to check stuff like this is in test tools
like lavc/tests/avcodec.c, but I don't think it is worth it for fftools).

> +
> +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
>  
> -    if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
> +    if (formatter->priv_size && !((tctx->priv = av_mallocz(formatter->priv_size)))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
> @@ -215,15 +218,15 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
>  
>      /* validate replace string */
>      {
> -        const uint8_t *p = tctx->string_validation_replacement;
> -        const uint8_t *endp = p + strlen(p);
> +        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
> +        const uint8_t *endp = p + strlen((const char *)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);
> +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);

This adds a memleak on data where it makes a difference.

>                  bprint_bytes(&bp, p0, p - p0),
>                      av_log(tctx, AV_LOG_ERROR,
>                             "Invalid UTF8 sequence %s found in string validation replace '%s'\n",
> @@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
>  
>  void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
>  {
> +    if (!tctx || section_id < 0 || section_id >= tctx->nb_sections)
> +        return;

Can this happen?

> +
>      tctx->level++;
>      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
>  
> @@ -272,6 +278,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
>  
>  void avtext_print_section_footer(AVTextFormatContext *tctx)
>  {
> +    if (!tctx || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return;

Can this happen?

> +
>      int section_id = tctx->section[tctx->level]->id;
>      int parent_section_id = tctx->level
>          ? tctx->section[tctx->level - 1]->id
> @@ -289,7 +298,12 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
>  
>  void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
>  {
> -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
> +    const AVTextFormatSection *section;
> +
> +    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return;

Can this happen?

> +
> +    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);
> @@ -299,24 +313,28 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
>  
>  static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
>  {
> -    const uint8_t *p, *endp;
> +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
>      AVBPrint dstbuf;
> +    AVBPrint bp;
>      int invalid_chars_nb = 0, ret = 0;
>  
> +    if (!tctx || !dstp || !src)
> +        return AVERROR(EINVAL);
> +

Can this happen?

> +    *dstp = NULL;
>      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
> +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>  
> -    endp = src + strlen(src);
> -    for (p = src; *p;) {
> -        uint32_t code;
> +    endp = srcp + strlen(src);
> +    for (p = srcp; *p;) {
> +        int32_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);
> +            av_bprint_clear(&bp);
> +            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;
>          }
>  
> @@ -336,7 +354,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
>          }
>  
>          if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
> -            av_bprint_append_data(&dstbuf, p0, p-p0);
> +            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
>      }
>  
>      if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
> @@ -346,6 +364,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
>  
>  end:
>      av_bprint_finalize(&dstbuf, dstp);
> +    av_bprint_finalize(&bp, NULL);
>      return ret;
>  }
>  
> @@ -358,17 +377,18 @@ struct unit_value {
>      const char *unit;
>  };
>  
> -static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
> +static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
>  {
>      double vald;
> -    int64_t vali;
> +    int64_t vali = 0;
>      int show_float = 0;
>  
>      if (uv.unit == unit_second_str) {
>          vald = uv.val.d;
>          show_float = 1;
>      } else {
> -        vald = vali = uv.val.i;
> +        vald = (double)uv.val.i;
> +        vali = uv.val.i;
>      }
>  
>      if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
> @@ -387,17 +407,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
>              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);
> +                index = (int64_t)(log2(vald) / 10);
> +                index = av_clip64(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);
> +                index = (int64_t)(log10(vald) / 3);
> +                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
>                  vald /= si_prefixes[index].dec_val;
>                  prefix_string = si_prefixes[index].dec_str;
>              }
> -            vali = vald;
> +            vali = (int64_t)vald;
>          }
>  
>          if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
> @@ -425,9 +445,14 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
>  
>  int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
>  {
> -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
> +    const AVTextFormatSection *section;
>      int ret = 0;
>  
> +    if (!tctx || !key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return AVERROR(EINVAL);

Can this happen?

> +
> +    section = tctx->section[tctx->level];
> +
>      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
>          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
>              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
> @@ -462,7 +487,7 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
>  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_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);

This is strictly worse than what was here before: With UNLIMITED you
would have a memleak in case the internal buffer wouldn't suffice.
(But anyway, this should use snprintf. I just sent a patch for this.)

>      av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
>      avtext_print_string(tctx, key, buf.str, 0);
>  }
> @@ -470,12 +495,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
>  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);
> +        char buf[128];
> +        double d = av_q2d(*time_base) * (double)ts;

We actually try to avoid explicit casts where possible.

>          struct unit_value uv;
>          uv.val.d = d;
>          uv.unit = unit_second_str;
> @@ -496,7 +520,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
>                         const uint8_t *data, int size)
>  {
>      AVBPrint bp;
> -    int offset = 0, l, i;
> +    unsigned offset = 0;
> +    int l, i;
>  
>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>      av_bprintf(&bp, "\n");
> @@ -523,25 +548,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
>  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 };
> +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
> +    int len;
>  
>      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);
> +    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
> +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);

Is it guaranteed that the output of snprintf() is not truncated?

>      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)
> +                           uint8_t *data, int size, const char *format,
> +                           int columns, int bytes, int offset_add)
>  {
>      AVBPrint bp;
> -    int offset = 0, l, i;
> +    unsigned offset = 0;
> +    int l, i;
> +
> +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
> +        return;

Can this happen?

>  
>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>      av_bprintf(&bp, "\n");
> @@ -607,12 +636,18 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
>      AVTextWriterContext *wctx;
>      int ret = 0;
>  
> -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
> +    if (!pwctx || !writer)
> +        return AVERROR(EINVAL);
> +
> +    if (!writer->priv_size && writer->priv_class)

Stuff like this should never happen and should therefore not be checked.

> +        return AVERROR(EINVAL);
> +
> +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
>  
> -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
> +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
> diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
> index 03564d14a7..e519094f4f 100644
> --- a/fftools/textformat/avtextformat.h
> +++ b/fftools/textformat/avtextformat.h
> @@ -21,9 +21,7 @@
>  #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"
> @@ -103,7 +101,7 @@ struct AVTextFormatContext {
>      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];
> +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
>      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
>                                                    ///  used by various formatters
>  
> @@ -124,7 +122,7 @@ struct AVTextFormatContext {
>  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
>  
>  int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
> -                        const struct AVTextFormatSection *sections, int nb_sections,
> +                        const AVTextFormatSection *sections, int nb_sections,
>                          int show_value_unit,
>                          int use_value_prefix,
>                          int use_byte_value_binary_prefix,
> diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
> index 14ef9fe8f9..3b05d25f36 100644
> --- a/fftools/textformat/tf_default.c
> +++ b/fftools/textformat/tf_default.c
> @@ -70,9 +70,10 @@ 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;
> +    unsigned i;
> +

Why not size_t?

>      for (i = 0; src[i] && i < dst_size - 1; i++)
> -        dst[i] = av_toupper(src[i]);
> +        dst[i] = (char)av_toupper(src[i]);
>      dst[i] = 0;
>      return dst;
>  }
> @@ -108,6 +109,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
>      const struct AVTextFormatSection *section = wctx->section[wctx->level];
>      char buf[32];
>  
> +    if (!section)
> +        return;

Can this happen?

> +
>      if (def->noprint_wrappers || def->nested_section[wctx->level])
>          return;
>  
> diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
> index 9e1aa60e09..ec471fd480 100644
> --- a/fftools/textformat/tf_ini.c
> +++ b/fftools/textformat/tf_ini.c
> @@ -92,7 +92,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
>              /* fallthrough */
>          default:
>              if ((unsigned char)c < 32)
> -                av_bprintf(dst, "\\x00%02x", c & 0xff);
> +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
>              else
>                  av_bprint_chars(dst, c, 1);
>              break;
> diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
> index 24838b35ec..f286838d3c 100644
> --- a/fftools/textformat/tf_json.c
> +++ b/fftools/textformat/tf_json.c
> @@ -82,13 +82,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
>      static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
>      const char *p;
>  
> +    if (!src) {
> +        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source string\n");
> +        return NULL;
> +    }

Can this even happen?

> +
>      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);
> +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
>          } else {
>              av_bprint_chars(dst, *p, 1);
>          }
> @@ -107,6 +112,7 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
>          wctx->section[wctx->level-1] : NULL;
>  
>      if (wctx->level && wctx->nb_item[wctx->level-1])
> +    if (wctx->level && wctx->nb_item[wctx->level - 1])
>          writer_put_str(wctx, ",\n");
>  
>      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
> diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
> index 76271dbaa6..eceeda81e5 100644
> --- a/fftools/textformat/tf_xml.c
> +++ b/fftools/textformat/tf_xml.c
> @@ -18,10 +18,7 @@
>   * 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"
> diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
> index d335d35a56..3c7492aa06 100644
> --- a/fftools/textformat/tw_avio.c
> +++ b/fftools/textformat/tw_avio.c
> @@ -63,7 +63,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
>  static void io_put_str(AVTextWriterContext *wctx, const char *str)
>  {
>      IOWriterContext *ctx = wctx->priv;
> -    avio_write(ctx->avio_context, str, strlen(str));
> +    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
>  }
>  
>  static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> @@ -89,10 +89,12 @@ const AVTextWriter avtextwriter_avio = {
>  
>  int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit)
>  {
> +    if (!pwctx || !output_filename || !output_filename[0])
> +        return AVERROR(EINVAL);

Can this happen?

> +
>      IOWriterContext *ctx;
>      int ret;
>  
> -
>      ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
>      if (ret < 0)
>          return ret;
> @@ -114,6 +116,9 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
>  
>  int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
>  {
> +    if (!pwctx || !avio_ctx)
> +        return AVERROR(EINVAL);
> +
>      IOWriterContext *ctx;
>      int ret;
>  

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements
  2025-04-15  1:05   ` Andreas Rheinhardt
@ 2025-04-15  3:19     ` softworkz .
  2025-04-16  4:50       ` Andreas Rheinhardt
  2025-04-16  9:52     ` softworkz .
  1 sibling, 1 reply; 130+ messages in thread
From: softworkz . @ 2025-04-15  3:19 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Andreas Rheinhardt
> Sent: Dienstag, 15. April 2025 03:06
> To: ffmpeg-devel@ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality
> improvements
> 
> softworkz:
> > From: softworkz <softworkz@hotmail.com>
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  fftools/textformat/avtextformat.c | 121 +++++++++++++++++++--------
> ---
> >  fftools/textformat/avtextformat.h |   6 +-
> >  fftools/textformat/tf_default.c   |   8 +-
> >  fftools/textformat/tf_ini.c       |   2 +-
> >  fftools/textformat/tf_json.c      |   8 +-
> >  fftools/textformat/tf_xml.c       |   3 -
> >  fftools/textformat/tw_avio.c      |   9 ++-
> >  7 files changed, 101 insertions(+), 56 deletions(-)
> >
> > diff --git a/fftools/textformat/avtextformat.c
> b/fftools/textformat/avtextformat.c
> > index 1ce51d11e2..406025d19d 100644
> > --- a/fftools/textformat/avtextformat.c
> > +++ b/fftools/textformat/avtextformat.c
> > @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
> >
> >  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++)
> > +    for (unsigned i = 0; i < ubuf_size; i++)
> 
> Why not size_t?

Because it creates more warnings about narrowing conversions.



> >          av_bprintf(bp, "%02X", ubuf[i]);
> >  }
> >
> > @@ -110,8 +109,6 @@ int avtext_context_close(AVTextFormatContext
> **ptctx)
> >
> >      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++)
> > @@ -141,12 +138,18 @@ int avtext_context_open(AVTextFormatContext
> **ptctx,
> >      AVTextFormatContext *tctx;
> >      int i, ret = 0;
> >
> > -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> > +    if (!ptctx || !formatter)
> > +        return AVERROR(EINVAL);
> 
> Can this happen?

see below

> > +
> > +    if (!formatter->priv_size && formatter->priv_class)
> > +        return AVERROR(EINVAL);
> 
> Stuff like this should never happen and should not be checked (or
> actually: the proper place to check stuff like this is in test tools
> like lavc/tests/avcodec.c, but I don't think it is worth it for
> fftools).

I probably overdid it a bit with checks, but the goal for this API 
is still to become public at some point (like in avutil), so I 
tried to move towards that direction already.




> > +
> > +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> >
> > -    if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
> > +    if (formatter->priv_size && !((tctx->priv =
> av_mallocz(formatter->priv_size)))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> > @@ -215,15 +218,15 @@ int avtext_context_open(AVTextFormatContext
> **ptctx,
> >
> >      /* validate replace string */
> >      {
> > -        const uint8_t *p = tctx->string_validation_replacement;
> > -        const uint8_t *endp = p + strlen(p);
> > +        const uint8_t *p = (uint8_t *)tctx-
> >string_validation_replacement;
> > +        const uint8_t *endp = p + strlen((const char *)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);
> > +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> 
> This adds a memleak on data where it makes a difference.

Why? The string_validation_replacement string should be short enough
to fit into the stack-allocated memory, no?


> >                  bprint_bytes(&bp, p0, p - p0),
> >                      av_log(tctx, AV_LOG_ERROR,
> >                             "Invalid UTF8 sequence %s found in
> string validation replace '%s'\n",
> > @@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] =
> "bit/s";
> >
> >  void avtext_print_section_header(AVTextFormatContext *tctx, const
> void *data, int section_id)
> >  {
> > +    if (!tctx || section_id < 0 || section_id >= tctx->nb_sections)
> > +        return;
> 
> Can this happen?

For a public API - many things can happen...


> >      tctx->level++;
> >      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
> >
> > @@ -272,6 +278,9 @@ void
> avtext_print_section_header(AVTextFormatContext *tctx, const void
> *data, in
> >
> >  void avtext_print_section_footer(AVTextFormatContext *tctx)
> >  {
> > +    if (!tctx || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return;
> 
> Can this happen?

Yes - when somewhere in FFmpeg some output is changed without thinking
about that there's a nesting limit of SECTION_MAX_NB_LEVELS.
Even when only 2 or 3 section types are defined, the level can go beyond
that value.


> 
> > +
> >      int section_id = tctx->section[tctx->level]->id;
> >      int parent_section_id = tctx->level
> >          ? tctx->section[tctx->level - 1]->id
> > @@ -289,7 +298,12 @@ void
> avtext_print_section_footer(AVTextFormatContext *tctx)
> >
> >  void avtext_print_integer(AVTextFormatContext *tctx, const char
> *key, int64_t val)
> >  {
> > -    const struct AVTextFormatSection *section = tctx->section[tctx-
> >level];
> > +    const AVTextFormatSection *section;
> > +
> > +    if (!tctx || !key || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return;
> 
> Can this happen?


see above


> 
> > +
> > +    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);
> > @@ -299,24 +313,28 @@ void avtext_print_integer(AVTextFormatContext
> *tctx, const char *key, int64_t va
> >
> >  static inline int validate_string(AVTextFormatContext *tctx, char
> **dstp, const char *src)
> >  {
> > -    const uint8_t *p, *endp;
> > +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
> >      AVBPrint dstbuf;
> > +    AVBPrint bp;
> >      int invalid_chars_nb = 0, ret = 0;
> >
> > +    if (!tctx || !dstp || !src)
> > +        return AVERROR(EINVAL);
> > +
> 
> Can this happen?
> 
> > +    *dstp = NULL;
> >      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
> > +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >
> > -    endp = src + strlen(src);
> > -    for (p = src; *p;) {
> > -        uint32_t code;
> > +    endp = srcp + strlen(src);
> > +    for (p = srcp; *p;) {
> > +        int32_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);
> > +            av_bprint_clear(&bp);
> > +            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;
> >          }
> >
> > @@ -336,7 +354,7 @@ static inline int
> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >          }
> >
> >          if (!invalid || tctx->string_validation ==
> AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
> > -            av_bprint_append_data(&dstbuf, p0, p-p0);
> > +            av_bprint_append_data(&dstbuf, (const char *)p0, p -
> p0);
> >      }
> >
> >      if (invalid_chars_nb && tctx->string_validation ==
> AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
> > @@ -346,6 +364,7 @@ static inline int
> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >
> >  end:
> >      av_bprint_finalize(&dstbuf, dstp);
> > +    av_bprint_finalize(&bp, NULL);
> >      return ret;
> >  }
> >
> > @@ -358,17 +377,18 @@ struct unit_value {
> >      const char *unit;
> >  };
> >
> > -static char *value_string(AVTextFormatContext *tctx, char *buf, int
> buf_size, struct unit_value uv)
> > +static char *value_string(const AVTextFormatContext *tctx, char
> *buf, int buf_size, struct unit_value uv)
> >  {
> >      double vald;
> > -    int64_t vali;
> > +    int64_t vali = 0;
> >      int show_float = 0;
> >
> >      if (uv.unit == unit_second_str) {
> >          vald = uv.val.d;
> >          show_float = 1;
> >      } else {
> > -        vald = vali = uv.val.i;
> > +        vald = (double)uv.val.i;
> > +        vali = uv.val.i;
> >      }
> >
> >      if (uv.unit == unit_second_str && tctx-
> >use_value_sexagesimal_format) {
> > @@ -387,17 +407,17 @@ static char *value_string(AVTextFormatContext
> *tctx, char *buf, int buf_size, st
> >              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);
> > +                index = (int64_t)(log2(vald) / 10);
> > +                index = av_clip64(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);
> > +                index = (int64_t)(log10(vald) / 3);
> > +                index = av_clip64(index, 0,
> FF_ARRAY_ELEMS(si_prefixes) - 1);
> >                  vald /= si_prefixes[index].dec_val;
> >                  prefix_string = si_prefixes[index].dec_str;
> >              }
> > -            vali = vald;
> > +            vali = (int64_t)vald;
> >          }
> >
> >          if (show_float || (tctx->use_value_prefix && vald !=
> (int64_t)vald))
> > @@ -425,9 +445,14 @@ void avtext_print_unit_int(AVTextFormatContext
> *tctx, const char *key, int value
> >
> >  int avtext_print_string(AVTextFormatContext *tctx, const char *key,
> const char *val, int flags)
> >  {
> > -    const struct AVTextFormatSection *section = tctx->section[tctx-
> >level];
> > +    const AVTextFormatSection *section;
> >      int ret = 0;
> >
> > +    if (!tctx || !key || !val || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return AVERROR(EINVAL);
> 
> Can this happen?
> 
> > +
> > +    section = tctx->section[tctx->level];
> > +
> >      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
> >          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
> >              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
> > @@ -462,7 +487,7 @@ int avtext_print_string(AVTextFormatContext
> *tctx, const char *key, const char *
> >  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_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
> 
> This is strictly worse than what was here before: With UNLIMITED you
> would have a memleak in case the internal buffer wouldn't suffice.
> (But anyway, this should use snprintf. I just sent a patch for this.)

To be honest, I don't see much value in AV_BPRINT_SIZE_AUTOMATIC.
When I would need to check the return values of all bprint operations,
all the convenience would go over board instantly. Using 
AV_BPRINT_SIZE_AUTOMATIC without return value checking is error-prone
and can cause errors which might be hard to identify.
On the other side, identifying places in code where AV_BPRINT_SIZE_UNLIMITED
is used and finalize is needed is a lot easier and doesn't even need
a specific case and/or debugging to find out.

In the worst case, I'd still prefer a memory leak over incorrect 
behavior (or well - always depends on the case). By that I don't mean 
errors that are reported and causing failure but those things that are
failing silently and are hard to notice or trace back when noticing.

Surely others may see it differently.



> 
> >      av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
> >      avtext_print_string(tctx, key, buf.str, 0);
> >  }
> > @@ -470,12 +495,11 @@ void avtext_print_rational(AVTextFormatContext
> *tctx, const char *key, AVRationa
> >  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);
> > +        char buf[128];
> > +        double d = av_q2d(*time_base) * (double)ts;
> 
> We actually try to avoid explicit casts where possible.


I'll answer that separately.



> >          struct unit_value uv;
> >          uv.val.d = d;
> >          uv.unit = unit_second_str;
> > @@ -496,7 +520,8 @@ void avtext_print_data(AVTextFormatContext
> *tctx, const char *name,
> >                         const uint8_t *data, int size)
> >  {
> >      AVBPrint bp;
> > -    int offset = 0, l, i;
> > +    unsigned offset = 0;
> > +    int l, i;
> >
> >      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >      av_bprintf(&bp, "\n");
> > @@ -523,25 +548,29 @@ void avtext_print_data(AVTextFormatContext
> *tctx, const char *name,
> >  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 };
> > +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
> > +    int len;
> >
> >      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);
> > +    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx-
> >hash));
> > +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len],
> (int)sizeof(buf) - len);
> 
> Is it guaranteed that the output of snprintf() is not truncated?

MAX_HASH_NAME_SIZE is 11 and AV_HASH_MAX_SIZE 64, make 192 - 11 > 0


> 
> >      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)
> > +                           uint8_t *data, int size, const char
> *format,
> > +                           int columns, int bytes, int offset_add)
> >  {
> >      AVBPrint bp;
> > -    int offset = 0, l, i;
> > +    unsigned offset = 0;
> > +    int l, i;
> > +
> > +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
> > +        return;
> 
> Can this happen?

Sure, as a public API. Of course, one can spend time, trying to determine
which conditions are realistically possible or not. But that introduces
potential of human error, so - unless it's a really hot path, one check
to many is better than one too less.


> 
> >
> >      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >      av_bprintf(&bp, "\n");
> > @@ -607,12 +636,18 @@ int
> avtextwriter_context_open(AVTextWriterContext **pwctx, const
> AVTextWriter *w
> >      AVTextWriterContext *wctx;
> >      int ret = 0;
> >
> > -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
> > +    if (!pwctx || !writer)
> > +        return AVERROR(EINVAL);
> > +
> > +    if (!writer->priv_size && writer->priv_class)
> 
> Stuff like this should never happen and should therefore not be
> checked.

OK.

> 
> > +        return AVERROR(EINVAL);
> > +
> > +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> >
> > -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
> > +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer-
> >priv_size)))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> > diff --git a/fftools/textformat/avtextformat.h
> b/fftools/textformat/avtextformat.h
> > index 03564d14a7..e519094f4f 100644
> > --- a/fftools/textformat/avtextformat.h
> > +++ b/fftools/textformat/avtextformat.h
> > @@ -21,9 +21,7 @@
> >  #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"
> > @@ -103,7 +101,7 @@ struct AVTextFormatContext {
> >      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];
> > +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
> >      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic
> print buffer dedicated to each section,
> >                                                    ///  used by
> various formatters
> >
> > @@ -124,7 +122,7 @@ struct AVTextFormatContext {
> >  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
> >
> >  int avtext_context_open(AVTextFormatContext **ptctx, const
> AVTextFormatter *formatter, AVTextWriterContext *writer_context, const
> char *args,
> > -                        const struct AVTextFormatSection *sections,
> int nb_sections,
> > +                        const AVTextFormatSection *sections, int
> nb_sections,
> >                          int show_value_unit,
> >                          int use_value_prefix,
> >                          int use_byte_value_binary_prefix,
> > diff --git a/fftools/textformat/tf_default.c
> b/fftools/textformat/tf_default.c
> > index 14ef9fe8f9..3b05d25f36 100644
> > --- a/fftools/textformat/tf_default.c
> > +++ b/fftools/textformat/tf_default.c
> > @@ -70,9 +70,10 @@ 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;
> > +    unsigned i;
> > +
> 
> Why not size_t?

see above.

> 
> >      for (i = 0; src[i] && i < dst_size - 1; i++)
> > -        dst[i] = av_toupper(src[i]);
> > +        dst[i] = (char)av_toupper(src[i]);
> >      dst[i] = 0;
> >      return dst;
> >  }
> > @@ -108,6 +109,9 @@ static void
> default_print_section_footer(AVTextFormatContext *wctx)
> >      const struct AVTextFormatSection *section = wctx->section[wctx-
> >level];
> >      char buf[32];
> >
> > +    if (!section)
> > +        return;
> 
> Can this happen?

No, but here it should actually call the function in tf_internal and with
that it can happen. This must have gotten lost from rebasing.


> > +
> >      if (def->noprint_wrappers || def->nested_section[wctx->level])
> >          return;
> >
> > diff --git a/fftools/textformat/tf_ini.c
> b/fftools/textformat/tf_ini.c
> > index 9e1aa60e09..ec471fd480 100644
> > --- a/fftools/textformat/tf_ini.c
> > +++ b/fftools/textformat/tf_ini.c
> > @@ -92,7 +92,7 @@ static char *ini_escape_str(AVBPrint *dst, const
> char *src)
> >              /* fallthrough */
> >          default:
> >              if ((unsigned char)c < 32)
> > -                av_bprintf(dst, "\\x00%02x", c & 0xff);
> > +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
> >              else
> >                  av_bprint_chars(dst, c, 1);
> >              break;
> > diff --git a/fftools/textformat/tf_json.c
> b/fftools/textformat/tf_json.c
> > index 24838b35ec..f286838d3c 100644
> > --- a/fftools/textformat/tf_json.c
> > +++ b/fftools/textformat/tf_json.c
> > @@ -82,13 +82,18 @@ static const char *json_escape_str(AVBPrint
> *dst, const char *src, void *log_ctx
> >      static const char json_subst[]  = { '"', '\\',  'b',  'f',
> 'n',  'r',  't', 0 };
> >      const char *p;
> >
> > +    if (!src) {
> > +        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source
> string\n");
> > +        return NULL;
> > +    }
> 
> Can this even happen?
> 
> > +
> >      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);
> > +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
> >          } else {
> >              av_bprint_chars(dst, *p, 1);
> >          }
> > @@ -107,6 +112,7 @@ static void
> json_print_section_header(AVTextFormatContext *wctx, const void *dat
> >          wctx->section[wctx->level-1] : NULL;
> >
> >      if (wctx->level && wctx->nb_item[wctx->level-1])
> > +    if (wctx->level && wctx->nb_item[wctx->level - 1])
> >          writer_put_str(wctx, ",\n");
> >
> >      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
> > diff --git a/fftools/textformat/tf_xml.c
> b/fftools/textformat/tf_xml.c
> > index 76271dbaa6..eceeda81e5 100644
> > --- a/fftools/textformat/tf_xml.c
> > +++ b/fftools/textformat/tf_xml.c
> > @@ -18,10 +18,7 @@
> >   * 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"
> > diff --git a/fftools/textformat/tw_avio.c
> b/fftools/textformat/tw_avio.c
> > index d335d35a56..3c7492aa06 100644
> > --- a/fftools/textformat/tw_avio.c
> > +++ b/fftools/textformat/tw_avio.c
> > @@ -63,7 +63,7 @@ static void io_w8(AVTextWriterContext *wctx, int
> b)
> >  static void io_put_str(AVTextWriterContext *wctx, const char *str)
> >  {
> >      IOWriterContext *ctx = wctx->priv;
> > -    avio_write(ctx->avio_context, str, strlen(str));
> > +    avio_write(ctx->avio_context, (const unsigned char *)str,
> (int)strlen(str));
> >  }
> >
> >  static void io_printf(AVTextWriterContext *wctx, const char *fmt,
> ...)
> > @@ -89,10 +89,12 @@ const AVTextWriter avtextwriter_avio = {
> >
> >  int avtextwriter_create_file(AVTextWriterContext **pwctx, const
> char *output_filename, int close_on_uninit)
> >  {
> > +    if (!pwctx || !output_filename || !output_filename[0])
> > +        return AVERROR(EINVAL);
> 
> Can this happen?

When public - yes.


Generally, I wonder: don't you find it risky to make decisions about
function implementations that are based on knowledge about the calling
code? ("can this happen?")
I mean, the calling code doesn't know about the assumptions you 
were making and on which behavior the implementation might be relying on.

For this patchset - most importantly for everything in graphprint.c,
I had worked with the deliberate intention for checking everything that
can be checked. The motivation here is the fact that graph-printing
is always run (when configured), even when the FFmpeg run has failed
or errored, because those are often the most interesting cases for 
viewing the graph or getting the data. 
But in case of errors and abortion, we cannot make any assumptions 
anymore and the answer to "can this happen?" might be more often "yes"
than usual.

I hope you don't mind to postpone the removal of checks a little bit.
It doesn't feel right to me now, to go over and just blindly remove
all of them. I would rather like to discuss a strategy/pattern in this
regard about which checks should be made at which places and where
not, also in the light of possibly being promoted to a public API.
Finally, I'd also like to hear Stefanos opinion - it's mostly his 
code that we're moving around here 😊


Thanks
sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements
  2025-04-15  3:19     ` softworkz .
@ 2025-04-16  4:50       ` Andreas Rheinhardt
  2025-04-16  6:27         ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Andreas Rheinhardt @ 2025-04-16  4:50 UTC (permalink / raw)
  To: ffmpeg-devel

softworkz .:
> 
> 
>> -----Original Message-----
>> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
>> Andreas Rheinhardt
>> Sent: Dienstag, 15. April 2025 03:06
>> To: ffmpeg-devel@ffmpeg.org
>> Subject: Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality
>> improvements
>>
>> softworkz:
>>> From: softworkz <softworkz@hotmail.com>
>>>
>>> Signed-off-by: softworkz <softworkz@hotmail.com>
>>> ---
>>>  fftools/textformat/avtextformat.c | 121 +++++++++++++++++++--------
>> ---
>>>  fftools/textformat/avtextformat.h |   6 +-
>>>  fftools/textformat/tf_default.c   |   8 +-
>>>  fftools/textformat/tf_ini.c       |   2 +-
>>>  fftools/textformat/tf_json.c      |   8 +-
>>>  fftools/textformat/tf_xml.c       |   3 -
>>>  fftools/textformat/tw_avio.c      |   9 ++-
>>>  7 files changed, 101 insertions(+), 56 deletions(-)
>>>
>>> diff --git a/fftools/textformat/avtextformat.c
>> b/fftools/textformat/avtextformat.c
>>> index 1ce51d11e2..406025d19d 100644
>>> --- a/fftools/textformat/avtextformat.c
>>> +++ b/fftools/textformat/avtextformat.c
>>> @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
>>>
>>>  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++)
>>> +    for (unsigned i = 0; i < ubuf_size; i++)
>>
>> Why not size_t?
> 
> Because it creates more warnings about narrowing conversions.
> 
> 
> 
>>>          av_bprintf(bp, "%02X", ubuf[i]);
>>>  }
>>>
>>> @@ -110,8 +109,6 @@ int avtext_context_close(AVTextFormatContext
>> **ptctx)
>>>
>>>      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++)
>>> @@ -141,12 +138,18 @@ int avtext_context_open(AVTextFormatContext
>> **ptctx,
>>>      AVTextFormatContext *tctx;
>>>      int i, ret = 0;
>>>
>>> -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
>>> +    if (!ptctx || !formatter)
>>> +        return AVERROR(EINVAL);
>>
>> Can this happen?
> 
> see below
> 
>>> +
>>> +    if (!formatter->priv_size && formatter->priv_class)
>>> +        return AVERROR(EINVAL);
>>
>> Stuff like this should never happen and should not be checked (or
>> actually: the proper place to check stuff like this is in test tools
>> like lavc/tests/avcodec.c, but I don't think it is worth it for
>> fftools).
> 
> I probably overdid it a bit with checks, but the goal for this API 
> is still to become public at some point (like in avutil), so I 
> tried to move towards that direction already.
> 
> 
> 
> 
>>> +
>>> +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
>>>          ret = AVERROR(ENOMEM);
>>>          goto fail;
>>>      }
>>>
>>> -    if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
>>> +    if (formatter->priv_size && !((tctx->priv =
>> av_mallocz(formatter->priv_size)))) {
>>>          ret = AVERROR(ENOMEM);
>>>          goto fail;
>>>      }
>>> @@ -215,15 +218,15 @@ int avtext_context_open(AVTextFormatContext
>> **ptctx,
>>>
>>>      /* validate replace string */
>>>      {
>>> -        const uint8_t *p = tctx->string_validation_replacement;
>>> -        const uint8_t *endp = p + strlen(p);
>>> +        const uint8_t *p = (uint8_t *)tctx-
>>> string_validation_replacement;
>>> +        const uint8_t *endp = p + strlen((const char *)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);
>>> +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>>
>> This adds a memleak on data where it makes a difference.
> 
> Why? The string_validation_replacement string should be short enough
> to fit into the stack-allocated memory, no?
> 
> 
>>>                  bprint_bytes(&bp, p0, p - p0),
>>>                      av_log(tctx, AV_LOG_ERROR,
>>>                             "Invalid UTF8 sequence %s found in
>> string validation replace '%s'\n",
>>> @@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] =
>> "bit/s";
>>>
>>>  void avtext_print_section_header(AVTextFormatContext *tctx, const
>> void *data, int section_id)
>>>  {
>>> +    if (!tctx || section_id < 0 || section_id >= tctx->nb_sections)
>>> +        return;
>>
>> Can this happen?
> 
> For a public API - many things can happen...
> 
> 
>>>      tctx->level++;
>>>      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
>>>
>>> @@ -272,6 +278,9 @@ void
>> avtext_print_section_header(AVTextFormatContext *tctx, const void
>> *data, in
>>>
>>>  void avtext_print_section_footer(AVTextFormatContext *tctx)
>>>  {
>>> +    if (!tctx || tctx->level < 0 || tctx->level >=
>> SECTION_MAX_NB_LEVELS)
>>> +        return;
>>
>> Can this happen?
> 
> Yes - when somewhere in FFmpeg some output is changed without thinking
> about that there's a nesting limit of SECTION_MAX_NB_LEVELS.
> Even when only 2 or 3 section types are defined, the level can go beyond
> that value.
> 
> 
>>
>>> +
>>>      int section_id = tctx->section[tctx->level]->id;
>>>      int parent_section_id = tctx->level
>>>          ? tctx->section[tctx->level - 1]->id
>>> @@ -289,7 +298,12 @@ void
>> avtext_print_section_footer(AVTextFormatContext *tctx)
>>>
>>>  void avtext_print_integer(AVTextFormatContext *tctx, const char
>> *key, int64_t val)
>>>  {
>>> -    const struct AVTextFormatSection *section = tctx->section[tctx-
>>> level];
>>> +    const AVTextFormatSection *section;
>>> +
>>> +    if (!tctx || !key || tctx->level < 0 || tctx->level >=
>> SECTION_MAX_NB_LEVELS)
>>> +        return;
>>
>> Can this happen?
> 
> 
> see above
> 
> 
>>
>>> +
>>> +    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);
>>> @@ -299,24 +313,28 @@ void avtext_print_integer(AVTextFormatContext
>> *tctx, const char *key, int64_t va
>>>
>>>  static inline int validate_string(AVTextFormatContext *tctx, char
>> **dstp, const char *src)
>>>  {
>>> -    const uint8_t *p, *endp;
>>> +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
>>>      AVBPrint dstbuf;
>>> +    AVBPrint bp;
>>>      int invalid_chars_nb = 0, ret = 0;
>>>
>>> +    if (!tctx || !dstp || !src)
>>> +        return AVERROR(EINVAL);
>>> +
>>
>> Can this happen?
>>
>>> +    *dstp = NULL;
>>>      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
>>> +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>>>
>>> -    endp = src + strlen(src);
>>> -    for (p = src; *p;) {
>>> -        uint32_t code;
>>> +    endp = srcp + strlen(src);
>>> +    for (p = srcp; *p;) {
>>> +        int32_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);
>>> +            av_bprint_clear(&bp);
>>> +            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;
>>>          }
>>>
>>> @@ -336,7 +354,7 @@ static inline int
>> validate_string(AVTextFormatContext *tctx, char **dstp, const
>>>          }
>>>
>>>          if (!invalid || tctx->string_validation ==
>> AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
>>> -            av_bprint_append_data(&dstbuf, p0, p-p0);
>>> +            av_bprint_append_data(&dstbuf, (const char *)p0, p -
>> p0);
>>>      }
>>>
>>>      if (invalid_chars_nb && tctx->string_validation ==
>> AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
>>> @@ -346,6 +364,7 @@ static inline int
>> validate_string(AVTextFormatContext *tctx, char **dstp, const
>>>
>>>  end:
>>>      av_bprint_finalize(&dstbuf, dstp);
>>> +    av_bprint_finalize(&bp, NULL);
>>>      return ret;
>>>  }
>>>
>>> @@ -358,17 +377,18 @@ struct unit_value {
>>>      const char *unit;
>>>  };
>>>
>>> -static char *value_string(AVTextFormatContext *tctx, char *buf, int
>> buf_size, struct unit_value uv)
>>> +static char *value_string(const AVTextFormatContext *tctx, char
>> *buf, int buf_size, struct unit_value uv)
>>>  {
>>>      double vald;
>>> -    int64_t vali;
>>> +    int64_t vali = 0;
>>>      int show_float = 0;
>>>
>>>      if (uv.unit == unit_second_str) {
>>>          vald = uv.val.d;
>>>          show_float = 1;
>>>      } else {
>>> -        vald = vali = uv.val.i;
>>> +        vald = (double)uv.val.i;
>>> +        vali = uv.val.i;
>>>      }
>>>
>>>      if (uv.unit == unit_second_str && tctx-
>>> use_value_sexagesimal_format) {
>>> @@ -387,17 +407,17 @@ static char *value_string(AVTextFormatContext
>> *tctx, char *buf, int buf_size, st
>>>              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);
>>> +                index = (int64_t)(log2(vald) / 10);
>>> +                index = av_clip64(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);
>>> +                index = (int64_t)(log10(vald) / 3);
>>> +                index = av_clip64(index, 0,
>> FF_ARRAY_ELEMS(si_prefixes) - 1);
>>>                  vald /= si_prefixes[index].dec_val;
>>>                  prefix_string = si_prefixes[index].dec_str;
>>>              }
>>> -            vali = vald;
>>> +            vali = (int64_t)vald;
>>>          }
>>>
>>>          if (show_float || (tctx->use_value_prefix && vald !=
>> (int64_t)vald))
>>> @@ -425,9 +445,14 @@ void avtext_print_unit_int(AVTextFormatContext
>> *tctx, const char *key, int value
>>>
>>>  int avtext_print_string(AVTextFormatContext *tctx, const char *key,
>> const char *val, int flags)
>>>  {
>>> -    const struct AVTextFormatSection *section = tctx->section[tctx-
>>> level];
>>> +    const AVTextFormatSection *section;
>>>      int ret = 0;
>>>
>>> +    if (!tctx || !key || !val || tctx->level < 0 || tctx->level >=
>> SECTION_MAX_NB_LEVELS)
>>> +        return AVERROR(EINVAL);
>>
>> Can this happen?
>>
>>> +
>>> +    section = tctx->section[tctx->level];
>>> +
>>>      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
>>>          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
>>>              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
>>> @@ -462,7 +487,7 @@ int avtext_print_string(AVTextFormatContext
>> *tctx, const char *key, const char *
>>>  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_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
>>
>> This is strictly worse than what was here before: With UNLIMITED you
>> would have a memleak in case the internal buffer wouldn't suffice.
>> (But anyway, this should use snprintf. I just sent a patch for this.)
> 
> To be honest, I don't see much value in AV_BPRINT_SIZE_AUTOMATIC.
> When I would need to check the return values of all bprint operations,
> all the convenience would go over board instantly. Using 
> AV_BPRINT_SIZE_AUTOMATIC without return value checking is error-prone
> and can cause errors which might be hard to identify.
> On the other side, identifying places in code where AV_BPRINT_SIZE_UNLIMITED
> is used and finalize is needed is a lot easier and doesn't even need
> a specific case and/or debugging to find out.
> 
> In the worst case, I'd still prefer a memory leak over incorrect 
> behavior (or well - always depends on the case). By that I don't mean 
> errors that are reported and causing failure but those things that are
> failing silently and are hard to notice or trace back when noticing.
> 
> Surely others may see it differently.
> 
> 
> 
>>
>>>      av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
>>>      avtext_print_string(tctx, key, buf.str, 0);
>>>  }
>>> @@ -470,12 +495,11 @@ void avtext_print_rational(AVTextFormatContext
>> *tctx, const char *key, AVRationa
>>>  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);
>>> +        char buf[128];
>>> +        double d = av_q2d(*time_base) * (double)ts;
>>
>> We actually try to avoid explicit casts where possible.
> 
> 
> I'll answer that separately.
> 
> 
> 
>>>          struct unit_value uv;
>>>          uv.val.d = d;
>>>          uv.unit = unit_second_str;
>>> @@ -496,7 +520,8 @@ void avtext_print_data(AVTextFormatContext
>> *tctx, const char *name,
>>>                         const uint8_t *data, int size)
>>>  {
>>>      AVBPrint bp;
>>> -    int offset = 0, l, i;
>>> +    unsigned offset = 0;
>>> +    int l, i;
>>>
>>>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>>>      av_bprintf(&bp, "\n");
>>> @@ -523,25 +548,29 @@ void avtext_print_data(AVTextFormatContext
>> *tctx, const char *name,
>>>  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 };
>>> +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
>>> +    int len;
>>>
>>>      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);
>>> +    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx-
>>> hash));
>>> +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len],
>> (int)sizeof(buf) - len);
>>
>> Is it guaranteed that the output of snprintf() is not truncated?
> 
> MAX_HASH_NAME_SIZE is 11 and AV_HASH_MAX_SIZE 64, make 192 - 11 > 0
> 
> 
>>
>>>      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)
>>> +                           uint8_t *data, int size, const char
>> *format,
>>> +                           int columns, int bytes, int offset_add)
>>>  {
>>>      AVBPrint bp;
>>> -    int offset = 0, l, i;
>>> +    unsigned offset = 0;
>>> +    int l, i;
>>> +
>>> +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
>>> +        return;
>>
>> Can this happen?
> 
> Sure, as a public API. Of course, one can spend time, trying to determine
> which conditions are realistically possible or not. But that introduces
> potential of human error, so - unless it's a really hot path, one check
> to many is better than one too less.
> 
> 
>>
>>>
>>>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>>>      av_bprintf(&bp, "\n");
>>> @@ -607,12 +636,18 @@ int
>> avtextwriter_context_open(AVTextWriterContext **pwctx, const
>> AVTextWriter *w
>>>      AVTextWriterContext *wctx;
>>>      int ret = 0;
>>>
>>> -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
>>> +    if (!pwctx || !writer)
>>> +        return AVERROR(EINVAL);
>>> +
>>> +    if (!writer->priv_size && writer->priv_class)
>>
>> Stuff like this should never happen and should therefore not be
>> checked.
> 
> OK.
> 
>>
>>> +        return AVERROR(EINVAL);
>>> +
>>> +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
>>>          ret = AVERROR(ENOMEM);
>>>          goto fail;
>>>      }
>>>
>>> -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
>>> +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer-
>>> priv_size)))) {
>>>          ret = AVERROR(ENOMEM);
>>>          goto fail;
>>>      }
>>> diff --git a/fftools/textformat/avtextformat.h
>> b/fftools/textformat/avtextformat.h
>>> index 03564d14a7..e519094f4f 100644
>>> --- a/fftools/textformat/avtextformat.h
>>> +++ b/fftools/textformat/avtextformat.h
>>> @@ -21,9 +21,7 @@
>>>  #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"
>>> @@ -103,7 +101,7 @@ struct AVTextFormatContext {
>>>      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];
>>> +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
>>>      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic
>> print buffer dedicated to each section,
>>>                                                    ///  used by
>> various formatters
>>>
>>> @@ -124,7 +122,7 @@ struct AVTextFormatContext {
>>>  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
>>>
>>>  int avtext_context_open(AVTextFormatContext **ptctx, const
>> AVTextFormatter *formatter, AVTextWriterContext *writer_context, const
>> char *args,
>>> -                        const struct AVTextFormatSection *sections,
>> int nb_sections,
>>> +                        const AVTextFormatSection *sections, int
>> nb_sections,
>>>                          int show_value_unit,
>>>                          int use_value_prefix,
>>>                          int use_byte_value_binary_prefix,
>>> diff --git a/fftools/textformat/tf_default.c
>> b/fftools/textformat/tf_default.c
>>> index 14ef9fe8f9..3b05d25f36 100644
>>> --- a/fftools/textformat/tf_default.c
>>> +++ b/fftools/textformat/tf_default.c
>>> @@ -70,9 +70,10 @@ 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;
>>> +    unsigned i;
>>> +
>>
>> Why not size_t?
> 
> see above.
> 
>>
>>>      for (i = 0; src[i] && i < dst_size - 1; i++)
>>> -        dst[i] = av_toupper(src[i]);
>>> +        dst[i] = (char)av_toupper(src[i]);
>>>      dst[i] = 0;
>>>      return dst;
>>>  }
>>> @@ -108,6 +109,9 @@ static void
>> default_print_section_footer(AVTextFormatContext *wctx)
>>>      const struct AVTextFormatSection *section = wctx->section[wctx-
>>> level];
>>>      char buf[32];
>>>
>>> +    if (!section)
>>> +        return;
>>
>> Can this happen?
> 
> No, but here it should actually call the function in tf_internal and with
> that it can happen. This must have gotten lost from rebasing.
> 
> 
>>> +
>>>      if (def->noprint_wrappers || def->nested_section[wctx->level])
>>>          return;
>>>
>>> diff --git a/fftools/textformat/tf_ini.c
>> b/fftools/textformat/tf_ini.c
>>> index 9e1aa60e09..ec471fd480 100644
>>> --- a/fftools/textformat/tf_ini.c
>>> +++ b/fftools/textformat/tf_ini.c
>>> @@ -92,7 +92,7 @@ static char *ini_escape_str(AVBPrint *dst, const
>> char *src)
>>>              /* fallthrough */
>>>          default:
>>>              if ((unsigned char)c < 32)
>>> -                av_bprintf(dst, "\\x00%02x", c & 0xff);
>>> +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
>>>              else
>>>                  av_bprint_chars(dst, c, 1);
>>>              break;
>>> diff --git a/fftools/textformat/tf_json.c
>> b/fftools/textformat/tf_json.c
>>> index 24838b35ec..f286838d3c 100644
>>> --- a/fftools/textformat/tf_json.c
>>> +++ b/fftools/textformat/tf_json.c
>>> @@ -82,13 +82,18 @@ static const char *json_escape_str(AVBPrint
>> *dst, const char *src, void *log_ctx
>>>      static const char json_subst[]  = { '"', '\\',  'b',  'f',
>> 'n',  'r',  't', 0 };
>>>      const char *p;
>>>
>>> +    if (!src) {
>>> +        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source
>> string\n");
>>> +        return NULL;
>>> +    }
>>
>> Can this even happen?
>>
>>> +
>>>      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);
>>> +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
>>>          } else {
>>>              av_bprint_chars(dst, *p, 1);
>>>          }
>>> @@ -107,6 +112,7 @@ static void
>> json_print_section_header(AVTextFormatContext *wctx, const void *dat
>>>          wctx->section[wctx->level-1] : NULL;
>>>
>>>      if (wctx->level && wctx->nb_item[wctx->level-1])
>>> +    if (wctx->level && wctx->nb_item[wctx->level - 1])
>>>          writer_put_str(wctx, ",\n");
>>>
>>>      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
>>> diff --git a/fftools/textformat/tf_xml.c
>> b/fftools/textformat/tf_xml.c
>>> index 76271dbaa6..eceeda81e5 100644
>>> --- a/fftools/textformat/tf_xml.c
>>> +++ b/fftools/textformat/tf_xml.c
>>> @@ -18,10 +18,7 @@
>>>   * 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"
>>> diff --git a/fftools/textformat/tw_avio.c
>> b/fftools/textformat/tw_avio.c
>>> index d335d35a56..3c7492aa06 100644
>>> --- a/fftools/textformat/tw_avio.c
>>> +++ b/fftools/textformat/tw_avio.c
>>> @@ -63,7 +63,7 @@ static void io_w8(AVTextWriterContext *wctx, int
>> b)
>>>  static void io_put_str(AVTextWriterContext *wctx, const char *str)
>>>  {
>>>      IOWriterContext *ctx = wctx->priv;
>>> -    avio_write(ctx->avio_context, str, strlen(str));
>>> +    avio_write(ctx->avio_context, (const unsigned char *)str,
>> (int)strlen(str));
>>>  }
>>>
>>>  static void io_printf(AVTextWriterContext *wctx, const char *fmt,
>> ...)
>>> @@ -89,10 +89,12 @@ const AVTextWriter avtextwriter_avio = {
>>>
>>>  int avtextwriter_create_file(AVTextWriterContext **pwctx, const
>> char *output_filename, int close_on_uninit)
>>>  {
>>> +    if (!pwctx || !output_filename || !output_filename[0])
>>> +        return AVERROR(EINVAL);
>>
>> Can this happen?
> 
> When public - yes.

Can it happen now?

> 
> 
> Generally, I wonder: don't you find it risky to make decisions about
> function implementations that are based on knowledge about the calling
> code? ("can this happen?")
> I mean, the calling code doesn't know about the assumptions you 
> were making and on which behavior the implementation might be relying on.

Not providing a pointer to store the AVTextWriterContext* is insane and
a programmer error. It should never happen. Doing so should lead to
undefined behavior (just as e.g. passing a NULL pointer to strlen()
does), even when public.

> 
> For this patchset - most importantly for everything in graphprint.c,
> I had worked with the deliberate intention for checking everything that
> can be checked. 

You should rather look at the callsites and ensure that they don't call
these functions in an insane way.

The motivation here is the fact that graph-printing
> is always run (when configured), even when the FFmpeg run has failed
> or errored, because those are often the most interesting cases for 
> viewing the graph or getting the data. 
> But in case of errors and abortion, we cannot make any assumptions 
> anymore and the answer to "can this happen?" might be more often "yes"
> than usual.

Of course we can make assumptions. All we need to do is check the ones
that can happen at the call site. Where they belong.

> 
> I hope you don't mind to postpone the removal of checks a little bit.

"Removal of checks"? This patch is about adding checks. Checks which IMO
should not be there in the first place.

> It doesn't feel right to me now, to go over and just blindly remove
> all of them. I would rather like to discuss a strategy/pattern in this
> regard about which checks should be made at which places and where
> not, also in the light of possibly being promoted to a public API.
> Finally, I'd also like to hear Stefanos opinion - it's mostly his 
> code that we're moving around here 😊

- Andreas

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements
  2025-04-16  4:50       ` Andreas Rheinhardt
@ 2025-04-16  6:27         ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-16  6:27 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Andreas Rheinhardt
> Sent: Mittwoch, 16. April 2025 06:51
> To: ffmpeg-devel@ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality
> improvements
> 
> softworkz .:
> >
> >
> >> -----Original Message-----
> >> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> >> Andreas Rheinhardt
> >> Sent: Dienstag, 15. April 2025 03:06
> >> To: ffmpeg-devel@ffmpeg.org
> >> Subject: Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality
> >> improvements
> >>
> >> softworkz:
> >>> From: softworkz <softworkz@hotmail.com>
> >>>
> >>> Signed-off-by: softworkz <softworkz@hotmail.com>
> >>> ---
> >>>  fftools/textformat/avtextformat.c | 121 +++++++++++++++++++------
> --
> >> ---
> >>>  fftools/textformat/avtextformat.h |   6 +-
> >>>  fftools/textformat/tf_default.c   |   8 +-
> >>>  fftools/textformat/tf_ini.c       |   2 +-
> >>>  fftools/textformat/tf_json.c      |   8 +-
> >>>  fftools/textformat/tf_xml.c       |   3 -
> >>>  fftools/textformat/tw_avio.c      |   9 ++-
> >>>  7 files changed, 101 insertions(+), 56 deletions(-)
> >>>
> >>> diff --git a/fftools/textformat/avtextformat.c
> >> b/fftools/textformat/avtextformat.c
> >>> index 1ce51d11e2..406025d19d 100644
> >>> --- a/fftools/textformat/avtextformat.c
> >>> +++ b/fftools/textformat/avtextformat.c
> >>> @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
> >>>
> >>>  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++)
> >>> +    for (unsigned i = 0; i < ubuf_size; i++)
> >>
> >> Why not size_t?
> >
> > Because it creates more warnings about narrowing conversions.
> >
> >
> >
> >>>          av_bprintf(bp, "%02X", ubuf[i]);
> >>>  }
> >>>
> >>> @@ -110,8 +109,6 @@ int avtext_context_close(AVTextFormatContext
> >> **ptctx)
> >>>
> >>>      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++)
> >>> @@ -141,12 +138,18 @@ int avtext_context_open(AVTextFormatContext
> >> **ptctx,
> >>>      AVTextFormatContext *tctx;
> >>>      int i, ret = 0;
> >>>
> >>> -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> >>> +    if (!ptctx || !formatter)
> >>> +        return AVERROR(EINVAL);
> >>
> >> Can this happen?
> >
> > see below
> >
> >>> +
> >>> +    if (!formatter->priv_size && formatter->priv_class)
> >>> +        return AVERROR(EINVAL);
> >>
> >> Stuff like this should never happen and should not be checked (or
> >> actually: the proper place to check stuff like this is in test
> tools
> >> like lavc/tests/avcodec.c, but I don't think it is worth it for
> >> fftools).
> >
> > I probably overdid it a bit with checks, but the goal for this API
> > is still to become public at some point (like in avutil), so I
> > tried to move towards that direction already.
> >
> >
> >
> >
> >>> +
> >>> +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
> >>>          ret = AVERROR(ENOMEM);
> >>>          goto fail;
> >>>      }
> >>>
> >>> -    if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
> >>> +    if (formatter->priv_size && !((tctx->priv =
> >> av_mallocz(formatter->priv_size)))) {
> >>>          ret = AVERROR(ENOMEM);
> >>>          goto fail;
> >>>      }
> >>> @@ -215,15 +218,15 @@ int avtext_context_open(AVTextFormatContext
> >> **ptctx,
> >>>
> >>>      /* validate replace string */
> >>>      {
> >>> -        const uint8_t *p = tctx->string_validation_replacement;
> >>> -        const uint8_t *endp = p + strlen(p);
> >>> +        const uint8_t *p = (uint8_t *)tctx-
> >>> string_validation_replacement;
> >>> +        const uint8_t *endp = p + strlen((const char *)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);
> >>> +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >>
> >> This adds a memleak on data where it makes a difference.
> >
> > Why? The string_validation_replacement string should be short enough
> > to fit into the stack-allocated memory, no?
> >
> >
> >>>                  bprint_bytes(&bp, p0, p - p0),
> >>>                      av_log(tctx, AV_LOG_ERROR,
> >>>                             "Invalid UTF8 sequence %s found in
> >> string validation replace '%s'\n",
> >>> @@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] =
> >> "bit/s";
> >>>
> >>>  void avtext_print_section_header(AVTextFormatContext *tctx, const
> >> void *data, int section_id)
> >>>  {
> >>> +    if (!tctx || section_id < 0 || section_id >= tctx-
> >nb_sections)
> >>> +        return;
> >>
> >> Can this happen?
> >
> > For a public API - many things can happen...
> >
> >
> >>>      tctx->level++;
> >>>      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
> >>>
> >>> @@ -272,6 +278,9 @@ void
> >> avtext_print_section_header(AVTextFormatContext *tctx, const void
> >> *data, in
> >>>
> >>>  void avtext_print_section_footer(AVTextFormatContext *tctx)
> >>>  {
> >>> +    if (!tctx || tctx->level < 0 || tctx->level >=
> >> SECTION_MAX_NB_LEVELS)
> >>> +        return;
> >>
> >> Can this happen?
> >
> > Yes - when somewhere in FFmpeg some output is changed without
> thinking
> > about that there's a nesting limit of SECTION_MAX_NB_LEVELS.
> > Even when only 2 or 3 section types are defined, the level can go
> beyond
> > that value.
> >
> >
> >>
> >>> +
> >>>      int section_id = tctx->section[tctx->level]->id;
> >>>      int parent_section_id = tctx->level
> >>>          ? tctx->section[tctx->level - 1]->id
> >>> @@ -289,7 +298,12 @@ void
> >> avtext_print_section_footer(AVTextFormatContext *tctx)
> >>>
> >>>  void avtext_print_integer(AVTextFormatContext *tctx, const char
> >> *key, int64_t val)
> >>>  {
> >>> -    const struct AVTextFormatSection *section = tctx-
> >section[tctx-
> >>> level];
> >>> +    const AVTextFormatSection *section;
> >>> +
> >>> +    if (!tctx || !key || tctx->level < 0 || tctx->level >=
> >> SECTION_MAX_NB_LEVELS)
> >>> +        return;
> >>
> >> Can this happen?
> >
> >
> > see above
> >
> >
> >>
> >>> +
> >>> +    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);
> >>> @@ -299,24 +313,28 @@ void
> avtext_print_integer(AVTextFormatContext
> >> *tctx, const char *key, int64_t va
> >>>
> >>>  static inline int validate_string(AVTextFormatContext *tctx, char
> >> **dstp, const char *src)
> >>>  {
> >>> -    const uint8_t *p, *endp;
> >>> +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
> >>>      AVBPrint dstbuf;
> >>> +    AVBPrint bp;
> >>>      int invalid_chars_nb = 0, ret = 0;
> >>>
> >>> +    if (!tctx || !dstp || !src)
> >>> +        return AVERROR(EINVAL);
> >>> +
> >>
> >> Can this happen?
> >>
> >>> +    *dstp = NULL;
> >>>      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
> >>> +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >>>
> >>> -    endp = src + strlen(src);
> >>> -    for (p = src; *p;) {
> >>> -        uint32_t code;
> >>> +    endp = srcp + strlen(src);
> >>> +    for (p = srcp; *p;) {
> >>> +        int32_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);
> >>> +            av_bprint_clear(&bp);
> >>> +            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;
> >>>          }
> >>>
> >>> @@ -336,7 +354,7 @@ static inline int
> >> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >>>          }
> >>>
> >>>          if (!invalid || tctx->string_validation ==
> >> AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
> >>> -            av_bprint_append_data(&dstbuf, p0, p-p0);
> >>> +            av_bprint_append_data(&dstbuf, (const char *)p0, p -
> >> p0);
> >>>      }
> >>>
> >>>      if (invalid_chars_nb && tctx->string_validation ==
> >> AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
> >>> @@ -346,6 +364,7 @@ static inline int
> >> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >>>
> >>>  end:
> >>>      av_bprint_finalize(&dstbuf, dstp);
> >>> +    av_bprint_finalize(&bp, NULL);
> >>>      return ret;
> >>>  }
> >>>
> >>> @@ -358,17 +377,18 @@ struct unit_value {
> >>>      const char *unit;
> >>>  };
> >>>
> >>> -static char *value_string(AVTextFormatContext *tctx, char *buf,
> int
> >> buf_size, struct unit_value uv)
> >>> +static char *value_string(const AVTextFormatContext *tctx, char
> >> *buf, int buf_size, struct unit_value uv)
> >>>  {
> >>>      double vald;
> >>> -    int64_t vali;
> >>> +    int64_t vali = 0;
> >>>      int show_float = 0;
> >>>
> >>>      if (uv.unit == unit_second_str) {
> >>>          vald = uv.val.d;
> >>>          show_float = 1;
> >>>      } else {
> >>> -        vald = vali = uv.val.i;
> >>> +        vald = (double)uv.val.i;
> >>> +        vali = uv.val.i;
> >>>      }
> >>>
> >>>      if (uv.unit == unit_second_str && tctx-
> >>> use_value_sexagesimal_format) {
> >>> @@ -387,17 +407,17 @@ static char
> *value_string(AVTextFormatContext
> >> *tctx, char *buf, int buf_size, st
> >>>              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);
> >>> +                index = (int64_t)(log2(vald) / 10);
> >>> +                index = av_clip64(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);
> >>> +                index = (int64_t)(log10(vald) / 3);
> >>> +                index = av_clip64(index, 0,
> >> FF_ARRAY_ELEMS(si_prefixes) - 1);
> >>>                  vald /= si_prefixes[index].dec_val;
> >>>                  prefix_string = si_prefixes[index].dec_str;
> >>>              }
> >>> -            vali = vald;
> >>> +            vali = (int64_t)vald;
> >>>          }
> >>>
> >>>          if (show_float || (tctx->use_value_prefix && vald !=
> >> (int64_t)vald))
> >>> @@ -425,9 +445,14 @@ void
> avtext_print_unit_int(AVTextFormatContext
> >> *tctx, const char *key, int value
> >>>
> >>>  int avtext_print_string(AVTextFormatContext *tctx, const char
> *key,
> >> const char *val, int flags)
> >>>  {
> >>> -    const struct AVTextFormatSection *section = tctx-
> >section[tctx-
> >>> level];
> >>> +    const AVTextFormatSection *section;
> >>>      int ret = 0;
> >>>
> >>> +    if (!tctx || !key || !val || tctx->level < 0 || tctx->level
> >=
> >> SECTION_MAX_NB_LEVELS)
> >>> +        return AVERROR(EINVAL);
> >>
> >> Can this happen?
> >>
> >>> +
> >>> +    section = tctx->section[tctx->level];
> >>> +
> >>>      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER
> ||
> >>>          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
> >>>              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
> >>> @@ -462,7 +487,7 @@ int avtext_print_string(AVTextFormatContext
> >> *tctx, const char *key, const char *
> >>>  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_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
> >>
> >> This is strictly worse than what was here before: With UNLIMITED
> you
> >> would have a memleak in case the internal buffer wouldn't suffice.
> >> (But anyway, this should use snprintf. I just sent a patch for
> this.)
> >
> > To be honest, I don't see much value in AV_BPRINT_SIZE_AUTOMATIC.
> > When I would need to check the return values of all bprint
> operations,
> > all the convenience would go over board instantly. Using
> > AV_BPRINT_SIZE_AUTOMATIC without return value checking is error-
> prone
> > and can cause errors which might be hard to identify.
> > On the other side, identifying places in code where
> AV_BPRINT_SIZE_UNLIMITED
> > is used and finalize is needed is a lot easier and doesn't even need
> > a specific case and/or debugging to find out.
> >
> > In the worst case, I'd still prefer a memory leak over incorrect
> > behavior (or well - always depends on the case). By that I don't
> mean
> > errors that are reported and causing failure but those things that
> are
> > failing silently and are hard to notice or trace back when noticing.
> >
> > Surely others may see it differently.
> >
> >
> >
> >>
> >>>      av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
> >>>      avtext_print_string(tctx, key, buf.str, 0);
> >>>  }
> >>> @@ -470,12 +495,11 @@ void
> avtext_print_rational(AVTextFormatContext
> >> *tctx, const char *key, AVRationa
> >>>  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);
> >>> +        char buf[128];
> >>> +        double d = av_q2d(*time_base) * (double)ts;
> >>
> >> We actually try to avoid explicit casts where possible.
> >
> >
> > I'll answer that separately.
> >
> >
> >
> >>>          struct unit_value uv;
> >>>          uv.val.d = d;
> >>>          uv.unit = unit_second_str;
> >>> @@ -496,7 +520,8 @@ void avtext_print_data(AVTextFormatContext
> >> *tctx, const char *name,
> >>>                         const uint8_t *data, int size)
> >>>  {
> >>>      AVBPrint bp;
> >>> -    int offset = 0, l, i;
> >>> +    unsigned offset = 0;
> >>> +    int l, i;
> >>>
> >>>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >>>      av_bprintf(&bp, "\n");
> >>> @@ -523,25 +548,29 @@ void avtext_print_data(AVTextFormatContext
> >> *tctx, const char *name,
> >>>  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 };
> >>> +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
> >>> +    int len;
> >>>
> >>>      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);
> >>> +    len = snprintf(buf, sizeof(buf), "%s:",
> av_hash_get_name(tctx-
> >>> hash));
> >>> +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len],
> >> (int)sizeof(buf) - len);
> >>
> >> Is it guaranteed that the output of snprintf() is not truncated?
> >
> > MAX_HASH_NAME_SIZE is 11 and AV_HASH_MAX_SIZE 64, make 192 - 11 > 0
> >
> >
> >>
> >>>      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)
> >>> +                           uint8_t *data, int size, const char
> >> *format,
> >>> +                           int columns, int bytes, int
> offset_add)
> >>>  {
> >>>      AVBPrint bp;
> >>> -    int offset = 0, l, i;
> >>> +    unsigned offset = 0;
> >>> +    int l, i;
> >>> +
> >>> +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
> >>> +        return;
> >>
> >> Can this happen?
> >
> > Sure, as a public API. Of course, one can spend time, trying to
> determine
> > which conditions are realistically possible or not. But that
> introduces
> > potential of human error, so - unless it's a really hot path, one
> check
> > to many is better than one too less.
> >
> >
> >>
> >>>
> >>>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >>>      av_bprintf(&bp, "\n");
> >>> @@ -607,12 +636,18 @@ int
> >> avtextwriter_context_open(AVTextWriterContext **pwctx, const
> >> AVTextWriter *w
> >>>      AVTextWriterContext *wctx;
> >>>      int ret = 0;
> >>>
> >>> -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
> >>> +    if (!pwctx || !writer)
> >>> +        return AVERROR(EINVAL);
> >>> +
> >>> +    if (!writer->priv_size && writer->priv_class)
> >>
> >> Stuff like this should never happen and should therefore not be
> >> checked.
> >
> > OK.
> >
> >>
> >>> +        return AVERROR(EINVAL);
> >>> +
> >>> +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
> >>>          ret = AVERROR(ENOMEM);
> >>>          goto fail;
> >>>      }
> >>>
> >>> -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
> >>> +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer-
> >>> priv_size)))) {
> >>>          ret = AVERROR(ENOMEM);
> >>>          goto fail;
> >>>      }
> >>> diff --git a/fftools/textformat/avtextformat.h
> >> b/fftools/textformat/avtextformat.h
> >>> index 03564d14a7..e519094f4f 100644
> >>> --- a/fftools/textformat/avtextformat.h
> >>> +++ b/fftools/textformat/avtextformat.h
> >>> @@ -21,9 +21,7 @@
> >>>  #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"
> >>> @@ -103,7 +101,7 @@ struct AVTextFormatContext {
> >>>      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];
> >>> +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
> >>>      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic
> >> print buffer dedicated to each section,
> >>>                                                    ///  used by
> >> various formatters
> >>>
> >>> @@ -124,7 +122,7 @@ struct AVTextFormatContext {
> >>>  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
> >>>
> >>>  int avtext_context_open(AVTextFormatContext **ptctx, const
> >> AVTextFormatter *formatter, AVTextWriterContext *writer_context,
> const
> >> char *args,
> >>> -                        const struct AVTextFormatSection
> *sections,
> >> int nb_sections,
> >>> +                        const AVTextFormatSection *sections, int
> >> nb_sections,
> >>>                          int show_value_unit,
> >>>                          int use_value_prefix,
> >>>                          int use_byte_value_binary_prefix,
> >>> diff --git a/fftools/textformat/tf_default.c
> >> b/fftools/textformat/tf_default.c
> >>> index 14ef9fe8f9..3b05d25f36 100644
> >>> --- a/fftools/textformat/tf_default.c
> >>> +++ b/fftools/textformat/tf_default.c
> >>> @@ -70,9 +70,10 @@ 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;
> >>> +    unsigned i;
> >>> +
> >>
> >> Why not size_t?
> >
> > see above.
> >
> >>
> >>>      for (i = 0; src[i] && i < dst_size - 1; i++)
> >>> -        dst[i] = av_toupper(src[i]);
> >>> +        dst[i] = (char)av_toupper(src[i]);
> >>>      dst[i] = 0;
> >>>      return dst;
> >>>  }
> >>> @@ -108,6 +109,9 @@ static void
> >> default_print_section_footer(AVTextFormatContext *wctx)
> >>>      const struct AVTextFormatSection *section = wctx-
> >section[wctx-
> >>> level];
> >>>      char buf[32];
> >>>
> >>> +    if (!section)
> >>> +        return;
> >>
> >> Can this happen?
> >
> > No, but here it should actually call the function in tf_internal and
> with
> > that it can happen. This must have gotten lost from rebasing.
> >
> >
> >>> +
> >>>      if (def->noprint_wrappers || def->nested_section[wctx-
> >level])
> >>>          return;
> >>>
> >>> diff --git a/fftools/textformat/tf_ini.c
> >> b/fftools/textformat/tf_ini.c
> >>> index 9e1aa60e09..ec471fd480 100644
> >>> --- a/fftools/textformat/tf_ini.c
> >>> +++ b/fftools/textformat/tf_ini.c
> >>> @@ -92,7 +92,7 @@ static char *ini_escape_str(AVBPrint *dst, const
> >> char *src)
> >>>              /* fallthrough */
> >>>          default:
> >>>              if ((unsigned char)c < 32)
> >>> -                av_bprintf(dst, "\\x00%02x", c & 0xff);
> >>> +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
> >>>              else
> >>>                  av_bprint_chars(dst, c, 1);
> >>>              break;
> >>> diff --git a/fftools/textformat/tf_json.c
> >> b/fftools/textformat/tf_json.c
> >>> index 24838b35ec..f286838d3c 100644
> >>> --- a/fftools/textformat/tf_json.c
> >>> +++ b/fftools/textformat/tf_json.c
> >>> @@ -82,13 +82,18 @@ static const char *json_escape_str(AVBPrint
> >> *dst, const char *src, void *log_ctx
> >>>      static const char json_subst[]  = { '"', '\\',  'b',  'f',
> >> 'n',  'r',  't', 0 };
> >>>      const char *p;
> >>>
> >>> +    if (!src) {
> >>> +        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL
> source
> >> string\n");
> >>> +        return NULL;
> >>> +    }
> >>
> >> Can this even happen?
> >>
> >>> +
> >>>      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);
> >>> +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
> >>>          } else {
> >>>              av_bprint_chars(dst, *p, 1);
> >>>          }
> >>> @@ -107,6 +112,7 @@ static void
> >> json_print_section_header(AVTextFormatContext *wctx, const void
> *dat
> >>>          wctx->section[wctx->level-1] : NULL;
> >>>
> >>>      if (wctx->level && wctx->nb_item[wctx->level-1])
> >>> +    if (wctx->level && wctx->nb_item[wctx->level - 1])
> >>>          writer_put_str(wctx, ",\n");
> >>>
> >>>      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
> >>> diff --git a/fftools/textformat/tf_xml.c
> >> b/fftools/textformat/tf_xml.c
> >>> index 76271dbaa6..eceeda81e5 100644
> >>> --- a/fftools/textformat/tf_xml.c
> >>> +++ b/fftools/textformat/tf_xml.c
> >>> @@ -18,10 +18,7 @@
> >>>   * 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"
> >>> diff --git a/fftools/textformat/tw_avio.c
> >> b/fftools/textformat/tw_avio.c
> >>> index d335d35a56..3c7492aa06 100644
> >>> --- a/fftools/textformat/tw_avio.c
> >>> +++ b/fftools/textformat/tw_avio.c
> >>> @@ -63,7 +63,7 @@ static void io_w8(AVTextWriterContext *wctx, int
> >> b)
> >>>  static void io_put_str(AVTextWriterContext *wctx, const char
> *str)
> >>>  {
> >>>      IOWriterContext *ctx = wctx->priv;
> >>> -    avio_write(ctx->avio_context, str, strlen(str));
> >>> +    avio_write(ctx->avio_context, (const unsigned char *)str,
> >> (int)strlen(str));
> >>>  }
> >>>
> >>>  static void io_printf(AVTextWriterContext *wctx, const char *fmt,
> >> ...)
> >>> @@ -89,10 +89,12 @@ const AVTextWriter avtextwriter_avio = {
> >>>
> >>>  int avtextwriter_create_file(AVTextWriterContext **pwctx, const
> >> char *output_filename, int close_on_uninit)
> >>>  {
> >>> +    if (!pwctx || !output_filename || !output_filename[0])
> >>> +        return AVERROR(EINVAL);
> >>
> >> Can this happen?
> >
> > When public - yes.
> 
> Can it happen now?
> 
> >
> >
> > Generally, I wonder: don't you find it risky to make decisions about
> > function implementations that are based on knowledge about the
> calling
> > code? ("can this happen?")
> > I mean, the calling code doesn't know about the assumptions you
> > were making and on which behavior the implementation might be
> relying on.
> 
> Not providing a pointer to store the AVTextWriterContext* is insane
> and
> a programmer error. It should never happen. Doing so should lead to
> undefined behavior (just as e.g. passing a NULL pointer to strlen()
> does), even when public.

Hi Andreas,

for the AVTextWriterContext I agree, haven't made such checks in 
the formatters either. For other cases I think - it depends..


> > For this patchset - most importantly for everything in graphprint.c,
> > I had worked with the deliberate intention for checking everything
> that
> > can be checked.
> 
> You should rather look at the callsites and ensure that they don't
> call
> these functions in an insane way.
> The motivation here is the fact that graph-printing
> > is always run (when configured), even when the FFmpeg run has failed
> > or errored, because those are often the most interesting cases for
> > viewing the graph or getting the data.
> > But in case of errors and abortion, we cannot make any assumptions
> > anymore and the answer to "can this happen?" might be more often
> "yes"
> > than usual.
> 
> Of course we can make assumptions. All we need to do is check the ones
> that can happen at the call site. Where they belong.

And when somebody makes changes at those call sites? That person 
won't know anything about the assumptions we had made.


Though, in graphprint.c it's primarily about traversing through
many different structures to collect all the data. This is where I'm
saying that we cannot make any assumptions because we also do this 
when FFmpeg is erroring out and it's hardly feasible to foresee all
possible outcomes - that’s what I meant originally.


> >
> > I hope you don't mind to postpone the removal of checks a little
> bit.
> 
> "Removal of checks"? This patch is about adding checks. Checks which
> IMO
> should not be there in the first place.

I mean postponing the removal of checks from the commit that is adding
them.

Because:

> > It doesn't feel right to me now, to go over and just blindly remove
> > all of them. I would rather like to discuss a strategy/pattern in
> this
> > regard about which checks should be made at which places and where
> > not, also in the light of possibly being promoted to a public API.
> > Finally, I'd also like to hear Stefanos opinion - it's mostly his
> > code that we're moving around here 😊

I will follow-up on this subject shortly.

Thank you very much
sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements
  2025-04-15  1:05   ` Andreas Rheinhardt
  2025-04-15  3:19     ` softworkz .
@ 2025-04-16  9:52     ` softworkz .
  1 sibling, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-16  9:52 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Andreas Rheinhardt
> Sent: Dienstag, 15. April 2025 03:06
> To: ffmpeg-devel@ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality
> improvements
> 
> softworkz:
> > From: softworkz <softworkz@hotmail.com>
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  fftools/textformat/avtextformat.c | 121 +++++++++++++++++++--------
> ---
> >  fftools/textformat/avtextformat.h |   6 +-
> >  fftools/textformat/tf_default.c   |   8 +-
> >  fftools/textformat/tf_ini.c       |   2 +-
> >  fftools/textformat/tf_json.c      |   8 +-
> >  fftools/textformat/tf_xml.c       |   3 -
> >  fftools/textformat/tw_avio.c      |   9 ++-
> >  7 files changed, 101 insertions(+), 56 deletions(-)
> >
> > diff --git a/fftools/textformat/avtextformat.c
> b/fftools/textformat/avtextformat.c
> > index 1ce51d11e2..406025d19d 100644
> > --- a/fftools/textformat/avtextformat.c
> > +++ b/fftools/textformat/avtextformat.c
> > @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
> >
> >  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++)
> > +    for (unsigned i = 0; i < ubuf_size; i++)
> 
> Why not size_t?
> 
> >          av_bprintf(bp, "%02X", ubuf[i]);
> >  }
> >
> > @@ -110,8 +109,6 @@ int avtext_context_close(AVTextFormatContext
> **ptctx)
> >
> >      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++)
> > @@ -141,12 +138,18 @@ int avtext_context_open(AVTextFormatContext
> **ptctx,
> >      AVTextFormatContext *tctx;
> >      int i, ret = 0;
> >
> > -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> > +    if (!ptctx || !formatter)
> > +        return AVERROR(EINVAL);
> 
> Can this happen?
> 
> > +
> > +    if (!formatter->priv_size && formatter->priv_class)
> > +        return AVERROR(EINVAL);
> 
> Stuff like this should never happen and should not be checked (or
> actually: the proper place to check stuff like this is in test tools
> like lavc/tests/avcodec.c, but I don't think it is worth it for
> fftools).
> 
> > +
> > +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> >
> > -    if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
> > +    if (formatter->priv_size && !((tctx->priv =
> av_mallocz(formatter->priv_size)))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> > @@ -215,15 +218,15 @@ int avtext_context_open(AVTextFormatContext
> **ptctx,
> >
> >      /* validate replace string */
> >      {
> > -        const uint8_t *p = tctx->string_validation_replacement;
> > -        const uint8_t *endp = p + strlen(p);
> > +        const uint8_t *p = (uint8_t *)tctx-
> >string_validation_replacement;
> > +        const uint8_t *endp = p + strlen((const char *)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);
> > +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> 
> This adds a memleak on data where it makes a difference.
> 
> >                  bprint_bytes(&bp, p0, p - p0),
> >                      av_log(tctx, AV_LOG_ERROR,
> >                             "Invalid UTF8 sequence %s found in
> string validation replace '%s'\n",
> > @@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] =
> "bit/s";
> >
> >  void avtext_print_section_header(AVTextFormatContext *tctx, const
> void *data, int section_id)
> >  {
> > +    if (!tctx || section_id < 0 || section_id >= tctx->nb_sections)
> > +        return;
> 
> Can this happen?
> 
> > +
> >      tctx->level++;
> >      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
> >
> > @@ -272,6 +278,9 @@ void
> avtext_print_section_header(AVTextFormatContext *tctx, const void
> *data, in
> >
> >  void avtext_print_section_footer(AVTextFormatContext *tctx)
> >  {
> > +    if (!tctx || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return;
> 
> Can this happen?
> 
> > +
> >      int section_id = tctx->section[tctx->level]->id;
> >      int parent_section_id = tctx->level
> >          ? tctx->section[tctx->level - 1]->id
> > @@ -289,7 +298,12 @@ void
> avtext_print_section_footer(AVTextFormatContext *tctx)
> >
> >  void avtext_print_integer(AVTextFormatContext *tctx, const char
> *key, int64_t val)
> >  {
> > -    const struct AVTextFormatSection *section = tctx->section[tctx-
> >level];
> > +    const AVTextFormatSection *section;
> > +
> > +    if (!tctx || !key || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return;
> 
> Can this happen?
> 
> > +
> > +    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);
> > @@ -299,24 +313,28 @@ void avtext_print_integer(AVTextFormatContext
> *tctx, const char *key, int64_t va
> >
> >  static inline int validate_string(AVTextFormatContext *tctx, char
> **dstp, const char *src)
> >  {
> > -    const uint8_t *p, *endp;
> > +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
> >      AVBPrint dstbuf;
> > +    AVBPrint bp;
> >      int invalid_chars_nb = 0, ret = 0;
> >
> > +    if (!tctx || !dstp || !src)
> > +        return AVERROR(EINVAL);
> > +
> 
> Can this happen?
> 
> > +    *dstp = NULL;
> >      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
> > +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >
> > -    endp = src + strlen(src);
> > -    for (p = src; *p;) {
> > -        uint32_t code;
> > +    endp = srcp + strlen(src);
> > +    for (p = srcp; *p;) {
> > +        int32_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);
> > +            av_bprint_clear(&bp);
> > +            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;
> >          }
> >
> > @@ -336,7 +354,7 @@ static inline int
> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >          }
> >
> >          if (!invalid || tctx->string_validation ==
> AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
> > -            av_bprint_append_data(&dstbuf, p0, p-p0);
> > +            av_bprint_append_data(&dstbuf, (const char *)p0, p -
> p0);
> >      }
> >
> >      if (invalid_chars_nb && tctx->string_validation ==
> AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
> > @@ -346,6 +364,7 @@ static inline int
> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >
> >  end:
> >      av_bprint_finalize(&dstbuf, dstp);
> > +    av_bprint_finalize(&bp, NULL);
> >      return ret;
> >  }
> >
> > @@ -358,17 +377,18 @@ struct unit_value {
> >      const char *unit;
> >  };
> >
> > -static char *value_string(AVTextFormatContext *tctx, char *buf, int
> buf_size, struct unit_value uv)
> > +static char *value_string(const AVTextFormatContext *tctx, char
> *buf, int buf_size, struct unit_value uv)
> >  {
> >      double vald;
> > -    int64_t vali;
> > +    int64_t vali = 0;
> >      int show_float = 0;
> >
> >      if (uv.unit == unit_second_str) {
> >          vald = uv.val.d;
> >          show_float = 1;
> >      } else {
> > -        vald = vali = uv.val.i;
> > +        vald = (double)uv.val.i;
> > +        vali = uv.val.i;
> >      }
> >
> >      if (uv.unit == unit_second_str && tctx-
> >use_value_sexagesimal_format) {
> > @@ -387,17 +407,17 @@ static char *value_string(AVTextFormatContext
> *tctx, char *buf, int buf_size, st
> >              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);
> > +                index = (int64_t)(log2(vald) / 10);
> > +                index = av_clip64(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);
> > +                index = (int64_t)(log10(vald) / 3);
> > +                index = av_clip64(index, 0,
> FF_ARRAY_ELEMS(si_prefixes) - 1);
> >                  vald /= si_prefixes[index].dec_val;
> >                  prefix_string = si_prefixes[index].dec_str;
> >              }
> > -            vali = vald;
> > +            vali = (int64_t)vald;
> >          }
> >
> >          if (show_float || (tctx->use_value_prefix && vald !=
> (int64_t)vald))
> > @@ -425,9 +445,14 @@ void avtext_print_unit_int(AVTextFormatContext
> *tctx, const char *key, int value
> >
> >  int avtext_print_string(AVTextFormatContext *tctx, const char *key,
> const char *val, int flags)
> >  {
> > -    const struct AVTextFormatSection *section = tctx->section[tctx-
> >level];
> > +    const AVTextFormatSection *section;
> >      int ret = 0;
> >
> > +    if (!tctx || !key || !val || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return AVERROR(EINVAL);
> 
> Can this happen?
> 
> > +
> > +    section = tctx->section[tctx->level];
> > +
> >      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
> >          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
> >              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
> > @@ -462,7 +487,7 @@ int avtext_print_string(AVTextFormatContext
> *tctx, const char *key, const char *
> >  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_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
> 
> This is strictly worse than what was here before: With UNLIMITED you
> would have a memleak in case the internal buffer wouldn't suffice.
> (But anyway, this should use snprintf. I just sent a patch for this.)
> 
> >      av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
> >      avtext_print_string(tctx, key, buf.str, 0);
> >  }
> > @@ -470,12 +495,11 @@ void avtext_print_rational(AVTextFormatContext
> *tctx, const char *key, AVRationa
> >  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);
> > +        char buf[128];
> > +        double d = av_q2d(*time_base) * (double)ts;
> 
> We actually try to avoid explicit casts where possible.
> 
> >          struct unit_value uv;
> >          uv.val.d = d;
> >          uv.unit = unit_second_str;
> > @@ -496,7 +520,8 @@ void avtext_print_data(AVTextFormatContext
> *tctx, const char *name,
> >                         const uint8_t *data, int size)
> >  {
> >      AVBPrint bp;
> > -    int offset = 0, l, i;
> > +    unsigned offset = 0;
> > +    int l, i;
> >
> >      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >      av_bprintf(&bp, "\n");
> > @@ -523,25 +548,29 @@ void avtext_print_data(AVTextFormatContext
> *tctx, const char *name,
> >  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 };
> > +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
> > +    int len;
> >
> >      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);
> > +    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx-
> >hash));
> > +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len],
> (int)sizeof(buf) - len);
> 
> Is it guaranteed that the output of snprintf() is not truncated?
> 
> >      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)
> > +                           uint8_t *data, int size, const char
> *format,
> > +                           int columns, int bytes, int offset_add)
> >  {
> >      AVBPrint bp;
> > -    int offset = 0, l, i;
> > +    unsigned offset = 0;
> > +    int l, i;
> > +
> > +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
> > +        return;
> 
> Can this happen?
> 
> >
> >      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >      av_bprintf(&bp, "\n");
> > @@ -607,12 +636,18 @@ int
> avtextwriter_context_open(AVTextWriterContext **pwctx, const
> AVTextWriter *w
> >      AVTextWriterContext *wctx;
> >      int ret = 0;
> >
> > -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
> > +    if (!pwctx || !writer)
> > +        return AVERROR(EINVAL);
> > +
> > +    if (!writer->priv_size && writer->priv_class)
> 
> Stuff like this should never happen and should therefore not be
> checked.
> 
> > +        return AVERROR(EINVAL);
> > +
> > +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> >
> > -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
> > +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer-
> >priv_size)))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> > diff --git a/fftools/textformat/avtextformat.h
> b/fftools/textformat/avtextformat.h
> > index 03564d14a7..e519094f4f 100644
> > --- a/fftools/textformat/avtextformat.h
> > +++ b/fftools/textformat/avtextformat.h
> > @@ -21,9 +21,7 @@
> >  #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"
> > @@ -103,7 +101,7 @@ struct AVTextFormatContext {
> >      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];
> > +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
> >      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic
> print buffer dedicated to each section,
> >                                                    ///  used by
> various formatters
> >
> > @@ -124,7 +122,7 @@ struct AVTextFormatContext {
> >  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
> >
> >  int avtext_context_open(AVTextFormatContext **ptctx, const
> AVTextFormatter *formatter, AVTextWriterContext *writer_context, const
> char *args,
> > -                        const struct AVTextFormatSection *sections,
> int nb_sections,
> > +                        const AVTextFormatSection *sections, int
> nb_sections,
> >                          int show_value_unit,
> >                          int use_value_prefix,
> >                          int use_byte_value_binary_prefix,
> > diff --git a/fftools/textformat/tf_default.c
> b/fftools/textformat/tf_default.c
> > index 14ef9fe8f9..3b05d25f36 100644
> > --- a/fftools/textformat/tf_default.c
> > +++ b/fftools/textformat/tf_default.c
> > @@ -70,9 +70,10 @@ 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;
> > +    unsigned i;
> > +
> 
> Why not size_t?
> 
> >      for (i = 0; src[i] && i < dst_size - 1; i++)
> > -        dst[i] = av_toupper(src[i]);
> > +        dst[i] = (char)av_toupper(src[i]);
> >      dst[i] = 0;
> >      return dst;
> >  }
> > @@ -108,6 +109,9 @@ static void
> default_print_section_footer(AVTextFormatContext *wctx)
> >      const struct AVTextFormatSection *section = wctx->section[wctx-
> >level];
> >      char buf[32];
> >
> > +    if (!section)
> > +        return;
> 
> Can this happen?
> 
> > +
> >      if (def->noprint_wrappers || def->nested_section[wctx->level])
> >          return;
> >
> > diff --git a/fftools/textformat/tf_ini.c
> b/fftools/textformat/tf_ini.c
> > index 9e1aa60e09..ec471fd480 100644
> > --- a/fftools/textformat/tf_ini.c
> > +++ b/fftools/textformat/tf_ini.c
> > @@ -92,7 +92,7 @@ static char *ini_escape_str(AVBPrint *dst, const
> char *src)
> >              /* fallthrough */
> >          default:
> >              if ((unsigned char)c < 32)
> > -                av_bprintf(dst, "\\x00%02x", c & 0xff);
> > +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
> >              else
> >                  av_bprint_chars(dst, c, 1);
> >              break;
> > diff --git a/fftools/textformat/tf_json.c
> b/fftools/textformat/tf_json.c
> > index 24838b35ec..f286838d3c 100644
> > --- a/fftools/textformat/tf_json.c
> > +++ b/fftools/textformat/tf_json.c
> > @@ -82,13 +82,18 @@ static const char *json_escape_str(AVBPrint
> *dst, const char *src, void *log_ctx
> >      static const char json_subst[]  = { '"', '\\',  'b',  'f',
> 'n',  'r',  't', 0 };
> >      const char *p;
> >
> > +    if (!src) {
> > +        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source
> string\n");
> > +        return NULL;
> > +    }
> 
> Can this even happen?
> 
> > +
> >      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);
> > +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
> >          } else {
> >              av_bprint_chars(dst, *p, 1);
> >          }
> > @@ -107,6 +112,7 @@ static void
> json_print_section_header(AVTextFormatContext *wctx, const void *dat
> >          wctx->section[wctx->level-1] : NULL;
> >
> >      if (wctx->level && wctx->nb_item[wctx->level-1])
> > +    if (wctx->level && wctx->nb_item[wctx->level - 1])
> >          writer_put_str(wctx, ",\n");
> >
> >      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
> > diff --git a/fftools/textformat/tf_xml.c
> b/fftools/textformat/tf_xml.c
> > index 76271dbaa6..eceeda81e5 100644
> > --- a/fftools/textformat/tf_xml.c
> > +++ b/fftools/textformat/tf_xml.c
> > @@ -18,10 +18,7 @@
> >   * 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"
> > diff --git a/fftools/textformat/tw_avio.c
> b/fftools/textformat/tw_avio.c
> > index d335d35a56..3c7492aa06 100644
> > --- a/fftools/textformat/tw_avio.c
> > +++ b/fftools/textformat/tw_avio.c
> > @@ -63,7 +63,7 @@ static void io_w8(AVTextWriterContext *wctx, int
> b)
> >  static void io_put_str(AVTextWriterContext *wctx, const char *str)
> >  {
> >      IOWriterContext *ctx = wctx->priv;
> > -    avio_write(ctx->avio_context, str, strlen(str));
> > +    avio_write(ctx->avio_context, (const unsigned char *)str,
> (int)strlen(str));
> >  }
> >
> >  static void io_printf(AVTextWriterContext *wctx, const char *fmt,
> ...)
> > @@ -89,10 +89,12 @@ const AVTextWriter avtextwriter_avio = {
> >
> >  int avtextwriter_create_file(AVTextWriterContext **pwctx, const
> char *output_filename, int close_on_uninit)
> >  {
> > +    if (!pwctx || !output_filename || !output_filename[0])
> > +        return AVERROR(EINVAL);
> 
> Can this happen?
> 
> > +
> >      IOWriterContext *ctx;
> >      int ret;
> >
> > -
> >      ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
> >      if (ret < 0)
> >          return ret;
> > @@ -114,6 +116,9 @@ int avtextwriter_create_file(AVTextWriterContext
> **pwctx, const char *output_fil
> >
> >  int avtextwriter_create_avio(AVTextWriterContext **pwctx,
> AVIOContext *avio_ctx, int close_on_uninit)
> >  {
> > +    if (!pwctx || !avio_ctx)
> > +        return AVERROR(EINVAL);
> > +
> >      IOWriterContext *ctx;
> >      int ret;
> >

Hi Andreas,

I have removed all the "impossible" checks and the checks for the 
context. Also applied the other changes.

Not sure whether it's helpful but here's a branch where the
changes from the review are in a single commit:

https://github.com/softworkz/FFmpeg/tree/execution_graph_printing_review1
https://github.com/softworkz/FFmpeg/commit/17b3cd7f881cd127020ab6fe55d6a955aa51e45f

The new patchset is coming in a few minutes (with squashed changes).

Thanks,
sw









_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing
  2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
                   ` (8 preceding siblings ...)
  2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 9/9] fftools/graphprint: Now, make it a Killer-Feature! softworkz
@ 2025-04-16 10:12 ` ffmpegagent
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 01/10] fftools/textformat: Formatting and whitespace changes softworkz
                     ` (11 more replies)
  9 siblings, 12 replies; 130+ messages in thread
From: ffmpegagent @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

Shortest cover letter for my longest-running FFmpeg patchset:

 * Apply
 * Build
 * Add the "-sg" switch to any FFmpeg command line
 * Press 'q' when you don't want to wait

SG = Show Graph

Documentation and examples can be found here:

https://github.com/softworkz/ffmpeg_output_apis/wiki


Version Updates
===============


V2
==

 * Rebased on top of Andreas' improvements
 * Applied changes from review (thanks, Andreas)

softworkz (10):
  fftools/textformat: Formatting and whitespace changes
  fftools/textformat: Quality improvements
  fftools/textformat: Introduce common header and deduplicate code
  fftools/tf_internal: Use ac_default_item_name
  fftools/textformat: Add function avtext_print_integer_flags()
  fftools/ffmpeg_filter: Move some declaration to new header file
  avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  fftools/resources: Add resource manager files
  fftools/graphprint: Add execution graph printing
  fftools/graphprint: Now, make it a Killer-Feature!

 doc/APIchanges                     |    3 +
 doc/ffmpeg.texi                    |   14 +
 ffbuild/common.mak                 |   28 +-
 fftools/Makefile                   |   18 +
 fftools/ffmpeg.c                   |    4 +
 fftools/ffmpeg.h                   |    4 +
 fftools/ffmpeg_filter.c            |  195 +----
 fftools/ffmpeg_filter.h            |  234 ++++++
 fftools/ffmpeg_opt.c               |   17 +
 fftools/graph/filelauncher.c       |  204 +++++
 fftools/graph/graphprint.c         | 1146 ++++++++++++++++++++++++++++
 fftools/graph/graphprint.h         |   62 ++
 fftools/resources/.gitignore       |    4 +
 fftools/resources/Makefile         |   27 +
 fftools/resources/graph.css        |  353 +++++++++
 fftools/resources/graph.html       |   86 +++
 fftools/resources/resman.c         |  213 ++++++
 fftools/resources/resman.h         |   50 ++
 fftools/textformat/avtextformat.c  |  238 +++---
 fftools/textformat/avtextformat.h  |   53 +-
 fftools/textformat/avtextwriters.h |   11 +-
 fftools/textformat/tf_compact.c    |  121 +--
 fftools/textformat/tf_default.c    |   55 +-
 fftools/textformat/tf_flat.c       |   51 +-
 fftools/textformat/tf_ini.c        |   62 +-
 fftools/textformat/tf_internal.h   |   81 ++
 fftools/textformat/tf_json.c       |   56 +-
 fftools/textformat/tf_mermaid.c    |  655 ++++++++++++++++
 fftools/textformat/tf_mermaid.h    |   41 +
 fftools/textformat/tf_xml.c        |   68 +-
 fftools/textformat/tw_avio.c       |   16 +-
 fftools/textformat/tw_buffer.c     |    7 +-
 fftools/textformat/tw_stdout.c     |    8 +-
 libavfilter/avfilter.c             |    9 +
 libavfilter/avfilter.h             |   12 +
 35 files changed, 3662 insertions(+), 544 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h
 create mode 100644 fftools/graph/filelauncher.c
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h
 create mode 100644 fftools/textformat/tf_internal.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h


base-commit: a888975a3c25760027cd59932f5c1ad04368db8b
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-66%2Fsoftworkz%2Fsubmit_print_execution_graph-v2
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-66/softworkz/submit_print_execution_graph-v2
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/66

Range-diff vs v1:

  1:  3df2018c81 !  1:  b6468d1a30 fftools/textformat: Formatting and whitespace changes
     @@ fftools/textformat/avtextformat.c: static const char *textcontext_get_formatter_
           { NULL }
       };
       
     -@@ fftools/textformat/avtextformat.c: int avtext_context_close(AVTextFormatContext **ptctx)
     +@@ fftools/textformat/avtextformat.c: void avtext_context_close(AVTextFormatContext **ptctx)
       }
       
       
     @@ fftools/textformat/avtextformat.c: int avtext_print_string(AVTextFormatContext *
      -                                         const char *key, AVRational q, char sep)
      +void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep)
       {
     -     AVBPrint buf;
     -     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
     +     char buf[44];
     +     snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
      @@ fftools/textformat/avtextformat.c: void avtext_print_rational(AVTextFormatContext *tctx,
       }
       
  2:  04cce91500 !  2:  6568269678 fftools/textformat: Quality improvements
     @@ fftools/textformat/avtextformat.c: static const AVClass textcontext_class = {
               av_bprintf(bp, "%02X", ubuf[i]);
       }
       
     -@@ fftools/textformat/avtextformat.c: int avtext_context_close(AVTextFormatContext **ptctx)
     - 
     -     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++)
      @@ fftools/textformat/avtextformat.c: int avtext_context_open(AVTextFormatContext      **ptctx,
           AVTextFormatContext *tctx;
           int i, ret = 0;
     @@ fftools/textformat/avtextformat.c: int avtext_context_open(AVTextFormatContext
      +    if (!ptctx || !formatter)
      +        return AVERROR(EINVAL);
      +
     -+    if (!formatter->priv_size && formatter->priv_class)
     -+        return AVERROR(EINVAL);
     -+
      +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
               ret = AVERROR(ENOMEM);
               goto fail;
           }
     - 
     --    if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
     -+    if (formatter->priv_size && !((tctx->priv = av_mallocz(formatter->priv_size)))) {
     -         ret = AVERROR(ENOMEM);
     -         goto fail;
     -     }
      @@ fftools/textformat/avtextformat.c: int avtext_context_open(AVTextFormatContext      **ptctx,
     +                     av_log(NULL, AV_LOG_ERROR, " %s", n);
     +                 av_log(NULL, AV_LOG_ERROR, "\n");
     +             }
     +-            return ret;
     ++            goto fail;
     +         }
       
           /* validate replace string */
           {
     @@ fftools/textformat/avtextformat.c: int avtext_context_open(AVTextFormatContext
                       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;
     ++                av_bprint_finalize(&bp, NULL);
     ++                goto fail;
     +             }
     +         }
     +     }
      @@ fftools/textformat/avtextformat.c: static const char unit_bit_per_second_str[] = "bit/s";
       
       void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
       {
     -+    if (!tctx || section_id < 0 || section_id >= tctx->nb_sections)
     ++    if (section_id < 0 || section_id >= tctx->nb_sections)
      +        return;
      +
           tctx->level++;
     @@ fftools/textformat/avtextformat.c: void avtext_print_section_header(AVTextFormat
       
       void avtext_print_section_footer(AVTextFormatContext *tctx)
       {
     -+    if (!tctx || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
     ++    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
      +        return;
      +
           int section_id = tctx->section[tctx->level]->id;
     @@ fftools/textformat/avtextformat.c: void avtext_print_section_footer(AVTextFormat
      -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
      +    const AVTextFormatSection *section;
      +
     -+    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
     ++    if (!key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
      +        return;
      +
      +    section = tctx->section[tctx->level];
     @@ fftools/textformat/avtextformat.c: void avtext_print_integer(AVTextFormatContext
      +    AVBPrint bp;
           int invalid_chars_nb = 0, ret = 0;
       
     -+    if (!tctx || !dstp || !src)
     -+        return AVERROR(EINVAL);
     -+
      +    *dstp = NULL;
           av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
      +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     @@ fftools/textformat/avtextformat.c: void avtext_print_unit_int(AVTextFormatContex
      +    const AVTextFormatSection *section;
           int ret = 0;
       
     -+    if (!tctx || !key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
     ++    if (!key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
      +        return AVERROR(EINVAL);
      +
      +    section = tctx->section[tctx->level];
     @@ fftools/textformat/avtextformat.c: void avtext_print_unit_int(AVTextFormatContex
           if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
               (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
                   && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
     -@@ fftools/textformat/avtextformat.c: int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
     - 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_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
     -     av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
     -     avtext_print_string(tctx, key, buf.str, 0);
     - }
      @@ fftools/textformat/avtextformat.c: void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
       void avtext_print_time(AVTextFormatContext *tctx, const char *key,
                              int64_t ts, const AVRational *time_base, int is_duration)
     @@ fftools/textformat/avtextformat.c: void avtext_print_rational(AVTextFormatContex
           } else {
      -        double d = ts * av_q2d(*time_base);
      +        char buf[128];
     -+        double d = av_q2d(*time_base) * (double)ts;
     ++        double d = av_q2d(*time_base) * ts;
               struct unit_value uv;
               uv.val.d = d;
               uv.unit = unit_second_str;
     @@ fftools/textformat/avtextformat.c: int avtextwriter_context_open(AVTextWriterCon
      +    if (!pwctx || !writer)
      +        return AVERROR(EINVAL);
      +
     -+    if (!writer->priv_size && writer->priv_class)
     -+        return AVERROR(EINVAL);
     -+
      +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
               ret = AVERROR(ENOMEM);
               goto fail;
     @@ fftools/textformat/tw_avio.c: static void io_w8(AVTextWriterContext *wctx, int b
       static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
      @@ fftools/textformat/tw_avio.c: const AVTextWriter avtextwriter_avio = {
       
     - int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit)
     + int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
       {
      +    if (!pwctx || !output_filename || !output_filename[0])
      +        return AVERROR(EINVAL);
  3:  0bd0ec42f1 !  3:  9e77a447b8 fftools/textformat: Introduce common header and deduplicate code
     @@ fftools/textformat/avtextwriters.h: typedef struct AVTextWriter {
      
       ## fftools/textformat/tf_compact.c ##
      @@
     - #include <string.h>
     - 
     - #include "avtextformat.h"
     -+#include "tf_internal.h"
     - #include <libavutil/mem.h>
     - #include <libavutil/avassert.h>
     - #include <libavutil/bprint.h>
     -@@
     - #include <libavutil/opt.h>
     - 
     - 
     + #include "libavutil/bprint.h"
     + #include "libavutil/error.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__)
     @@ fftools/textformat/tf_compact.c
      -    .item_name  = name##_get_name,                  \
      -    .option     = name##_options                    \
      -}
     --
     ++#include "tf_internal.h"
       
     - /* Compact output */
       
     + /* Compact output */
      @@ fftools/textformat/tf_compact.c: static av_cold int compact_init(AVTextFormatContext *wctx)
       static void compact_print_section_header(AVTextFormatContext *wctx, const void *data)
       {
     @@ fftools/textformat/tf_compact.c: static void compact_print_section_header(AVText
      
       ## fftools/textformat/tf_default.c ##
      @@
     - #include <string.h>
     - 
       #include "avtextformat.h"
     -+#include "tf_internal.h"
     - #include <libavutil/mem.h>
     - #include <libavutil/avassert.h>
     - #include <libavutil/bprint.h>
     - #include <libavutil/opt.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__)
     @@ fftools/textformat/tf_default.c
      -    .item_name  = name##_get_name,                  \
      -    .option     = name##_options                    \
      -}
     --
     ++#include "tf_internal.h"
     + 
       /* Default output */
       
     - typedef struct DefaultContext {
      @@ fftools/textformat/tf_default.c: static void default_print_section_header(AVTextFormatContext *wctx, const void *
       {
           DefaultContext *def = wctx->priv;
     @@ fftools/textformat/tf_default.c: static void default_print_section_header(AVText
      
       ## fftools/textformat/tf_flat.c ##
      @@
     - #include <string.h>
     - 
     - #include "avtextformat.h"
     -+#include "tf_internal.h"
     - #include <libavutil/mem.h>
     - #include <libavutil/avassert.h>
     - #include <libavutil/bprint.h>
     -@@
     - #include <libavutil/macros.h>
     - #include <libavutil/opt.h>
     - 
     + #include "libavutil/bprint.h"
     + #include "libavutil/error.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__)
     @@ fftools/textformat/tf_flat.c
      -    .option     = name##_options                    \
      -}
      -
     --
     --/* Flat output */
     -+ /* Flat output */
     ++#include "tf_internal.h"
     + 
     + /* Flat output */
       
     - typedef struct FlatContext {
     -     const AVClass *class;
      @@ fftools/textformat/tf_flat.c: static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
       {
           FlatContext *flat = wctx->priv;
     @@ fftools/textformat/tf_flat.c: static void flat_print_section_header(AVTextFormat
      
       ## fftools/textformat/tf_ini.c ##
      @@
     - #include <string.h>
     - 
     - #include "avtextformat.h"
     -+#include "tf_internal.h"
     - #include <libavutil/mem.h>
     - #include <libavutil/avassert.h>
     - #include <libavutil/bprint.h>
     - #include <libavutil/opt.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__)
     @@ fftools/textformat/tf_ini.c
      -    .item_name  = name##_get_name,                  \
      -    .option     = name##_options                    \
      -}
     --
     ++#include "tf_internal.h"
     + 
       /* Default output */
       
     - typedef struct DefaultContext {
      @@ fftools/textformat/tf_ini.c: static void ini_print_section_header(AVTextFormatContext *wctx, const void *data
       {
           INIContext *ini = wctx->priv;
     @@ fftools/textformat/tf_internal.h (new)
      
       ## fftools/textformat/tf_json.c ##
      @@
     - #include <string.h>
     - 
       #include "avtextformat.h"
     -+#include "tf_internal.h"
     - #include <libavutil/mem.h>
     - #include <libavutil/avassert.h>
     - #include <libavutil/bprint.h>
     - #include <libavutil/opt.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__)
     @@ fftools/textformat/tf_json.c
      -    .option     = name##_options                    \
      -}
      -
     --
     ++#include "tf_internal.h"
     + 
       /* JSON output */
       
     - typedef struct JSONContext {
      @@ fftools/textformat/tf_json.c: static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
       
       static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
     @@ fftools/textformat/tf_json.c: const AVTextFormatter avtextformatter_json = {
      
       ## fftools/textformat/tf_xml.c ##
      @@
     - #include <string.h>
     - 
     - #include "avtextformat.h"
     -+#include "tf_internal.h"
     - #include <libavutil/mem.h>
     - #include <libavutil/avassert.h>
     - #include <libavutil/bprint.h>
     -@@
     - #include <libavutil/macros.h>
     - #include <libavutil/opt.h>
     - 
     + #include "libavutil/bprint.h"
     + #include "libavutil/error.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__)
     @@ fftools/textformat/tf_xml.c
      -    .item_name  = name##_get_name,                  \
      -    .option     = name##_options                    \
      -}
     --
     ++#include "tf_internal.h"
     + 
       /* XML output */
       
     - typedef struct XMLContext {
      @@ fftools/textformat/tf_xml.c: static av_cold int xml_init(AVTextFormatContext *wctx)
       static void xml_print_section_header(AVTextFormatContext *wctx, const void *data)
       {
     @@ fftools/textformat/tw_avio.c: static void io_put_str(AVTextWriterContext *wctx,
       }
       
       
     +@@ fftools/textformat/tw_avio.c: const AVTextWriter avtextwriter_avio = {
     + 
     + int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
     + {
     +-    if (!pwctx || !output_filename || !output_filename[0])
     ++    if (!output_filename || !output_filename[0])
     +         return AVERROR(EINVAL);
     + 
     +     IOWriterContext *ctx;
      
       ## fftools/textformat/tw_buffer.c ##
      @@ fftools/textformat/tw_buffer.c: static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
  -:  ---------- >  4:  a1b358f5c5 fftools/tf_internal: Use ac_default_item_name
  4:  ce1191411c =  5:  4f6870ed4c fftools/textformat: Add function avtext_print_integer_flags()
  5:  5e0f818fd3 =  6:  9c03e66aea fftools/ffmpeg_filter: Move some declaration to new header file
  6:  4f3db399c6 =  7:  eb54476d00 avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  7:  898574385f =  8:  dac301adba fftools/resources: Add resource manager files
  8:  72aa40edd7 =  9:  128ae47177 fftools/graphprint: Add execution graph printing
  9:  3a61edc78d = 10:  0f6fd80b25 fftools/graphprint: Now, make it a Killer-Feature!

-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 01/10] fftools/textformat: Formatting and whitespace changes
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
@ 2025-04-16 10:12   ` softworkz
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat: Quality improvements softworkz
                     ` (10 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c  | 104 +++++++++++++++--------------
 fftools/textformat/avtextformat.h  |  16 ++---
 fftools/textformat/avtextwriters.h |  11 ++-
 fftools/textformat/tf_compact.c    |  91 ++++++++++++++-----------
 fftools/textformat/tf_default.c    |  20 +++---
 fftools/textformat/tf_flat.c       |  26 ++++----
 fftools/textformat/tf_ini.c        |  36 +++++-----
 fftools/textformat/tf_json.c       |  10 +--
 fftools/textformat/tf_xml.c        |  30 +++++----
 9 files changed, 183 insertions(+), 161 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 44085fc87a..811b14b999 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -34,9 +34,9 @@
 #include "libavutil/opt.h"
 #include "avtextformat.h"
 
-#define SECTION_ID_NONE -1
+#define SECTION_ID_NONE (-1)
 
-#define SHOW_OPTIONAL_FIELDS_AUTO       -1
+#define SHOW_OPTIONAL_FIELDS_AUTO      (-1)
 #define SHOW_OPTIONAL_FIELDS_NEVER       0
 #define SHOW_OPTIONAL_FIELDS_ALWAYS      1
 
@@ -64,14 +64,14 @@ static const char *textcontext_get_formatter_name(void *p)
 
 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" },
+      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"}},
+      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 }
 };
 
@@ -125,14 +125,18 @@ void avtext_context_close(AVTextFormatContext **ptctx)
 }
 
 
-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)
+int avtext_context_open(AVTextFormatContext      **ptctx,
+                        const AVTextFormatter     *formatter,
+                        AVTextWriterContext       *writer_context,
+                        const char                *args,
+                        const 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;
@@ -200,7 +204,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash) {
+    if (show_data_hash)
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -211,7 +215,6 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             return ret;
         }
-    }
 
     /* validate replace string */
     {
@@ -224,7 +227,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             if (ret < 0) {
                 AVBPrint bp;
                 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
-                bprint_bytes(&bp, p0, p-p0),
+                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);
@@ -248,15 +251,13 @@ fail:
 }
 
 /* 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_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)
+void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
@@ -272,8 +273,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx,
 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;
+    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]++;
@@ -285,8 +287,7 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
     tctx->level--;
 }
 
-void avtext_print_integer(AVTextFormatContext *tctx,
-                                        const char *key, int64_t val)
+void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
     const struct AVTextFormatSection *section = tctx->section[tctx->level];
 
@@ -324,11 +325,9 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
             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);
+                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);
@@ -340,11 +339,10 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
             av_bprint_append_data(&dstbuf, p0, p-p0);
     }
 
-    if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
+    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);
@@ -352,7 +350,11 @@ end:
 }
 
 struct unit_value {
-    union { double d; int64_t i; } val;
+    union {
+        double  d;
+        int64_t i;
+    } val;
+
     const char *unit;
 };
 
@@ -402,8 +404,9 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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 : "");
+                    prefix_string, tctx->show_value_unit ? uv.unit : "");
     }
 
     return buf;
@@ -427,8 +430,8 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 
     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)))
+            && (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)) {
@@ -440,11 +443,10 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
             if (ret < 0) goto end;
             tctx->formatter->print_string(tctx, key1, val1);
         end:
-            if (ret < 0) {
+            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 {
@@ -457,8 +459,7 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
     return ret;
 }
 
-void avtext_print_rational(AVTextFormatContext *tctx,
-                                         const char *key, AVRational q, char sep)
+void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep)
 {
     char buf[44];
     snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
@@ -466,7 +467,7 @@ void avtext_print_rational(AVTextFormatContext *tctx,
 }
 
 void avtext_print_time(AVTextFormatContext *tctx, const char *key,
-                              int64_t ts, const AVRational *time_base, int is_duration)
+                       int64_t ts, const AVRational *time_base, int is_duration)
 {
     char buf[128];
 
@@ -484,15 +485,14 @@ void avtext_print_time(AVTextFormatContext *tctx, const char *key,
 
 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)) {
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0))
         avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
-    } else {
+    else
         avtext_print_integer(tctx, key, ts);
-    }
 }
 
 void avtext_print_data(AVTextFormatContext *tctx, const char *name,
-                              const uint8_t *data, int size)
+                       const uint8_t *data, int size)
 {
     AVBPrint bp;
     int offset = 0, l, i;
@@ -520,12 +520,13 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 }
 
 void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
-                                   const uint8_t *data, int size)
+                            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));
@@ -551,7 +552,7 @@ void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
             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 --;
+            size--;
         }
         av_bprintf(&bp, "\n");
         offset += offset_add;
@@ -642,7 +643,8 @@ fail:
     return ret;
 }
 
-static const AVTextFormatter *registered_formatters[7+1];
+static const AVTextFormatter *registered_formatters[10 + 1];
+
 static void formatters_register_all(void)
 {
     static int initialized;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c2c56dc1a7..c598af3450 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -86,17 +86,17 @@ typedef struct AVTextFormatter {
 #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
+    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
+    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
+    const AVTextFormatSection *sections; ///< array containing all sections
+    int nb_sections;                   ///< number of sections
 
-    int level;                      ///< current level, starting from 0
+    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];
diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index c99d6b3548..34db3f1832 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -37,11 +37,11 @@ typedef struct AVTextWriter {
     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, ...);
+    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 {
@@ -49,7 +49,6 @@ typedef struct AVTextWriterContext {
     const AVTextWriter *writer;
     const char *name;
     void *priv;                     ///< private data for use by the writer
-
 } AVTextWriterContext;
 
 
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index 31bfc81513..d4ac296a42 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -58,10 +58,10 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 
     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 '\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)
@@ -78,6 +78,7 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 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)
@@ -114,16 +115,16 @@ typedef struct 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},
+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);
@@ -139,10 +140,13 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
     }
     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 {
+    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);
     }
@@ -162,8 +166,8 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
     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))))) {
+            (!(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 */
@@ -171,10 +175,10 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
         AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
 
         compact->nested_section[wctx->level] = 1;
-        compact->has_nested_elems[wctx->level-1] = 1;
+        compact->has_nested_elems[wctx->level - 1] = 1;
 
         av_bprintf(section_pbuf, "%s%s",
-                   wctx->section_pbuf[wctx->level-1].str, element_name);
+                   wctx->section_pbuf[wctx->level - 1].str, element_name);
 
         if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
             // add /TYPE to prefix
@@ -185,30 +189,33 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
                 char c =
                     (*p >= '0' && *p <= '9') ||
                     (*p >= 'a' && *p <= 'z') ||
-                    (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
+                    (*p >= 'A' && *p <= 'Z')
+                    ? (char)(char)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];
+        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])
+        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)))
+            !(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)
 {
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     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)))
+        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
         writer_w8(wctx, '\n');
 }
 
@@ -217,9 +224,12 @@ static void compact_print_str(AVTextFormatContext *wctx, const char *key, const
     CompactContext *compact = wctx->priv;
     AVBPrint buf;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
@@ -229,9 +239,12 @@ static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_
 {
     CompactContext *compact = wctx->priv;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
 }
 
@@ -253,15 +266,15 @@ const AVTextFormatter avtextformatter_compact = {
 #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},
+    { "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);
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 86582829e4..2c5047eafd 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -56,11 +56,11 @@ typedef struct DefaultContext {
 #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},
+    { "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);
@@ -69,7 +69,7 @@ DEFINE_FORMATTER_CLASS(default);
 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++)
+    for (i = 0; src[i] && i < dst_size - 1; i++)
         dst[i] = av_toupper(src[i]);
     dst[i] = 0;
     return dst;
@@ -85,10 +85,10 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 
     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))) {
+        !(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,
+                   wctx->section_pbuf[wctx->level - 1].str,
                    upcase_string(buf, sizeof(buf),
                                  av_x_if_null(section->element_name, section->name)));
     }
@@ -96,7 +96,7 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
     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)))
+    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));
 }
 
@@ -109,7 +109,7 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     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)))
+    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));
 }
 
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index 919d44bc6b..f692971bcc 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -57,12 +57,12 @@ typedef struct 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},
+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);
@@ -126,16 +126,18 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
     av_bprint_clear(buf);
     if (!parent_section)
         return;
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+
+    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))) {
+        !(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];
+            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);
         }
     }
@@ -166,6 +168,6 @@ const AVTextFormatter avtextformatter_flat = {
     .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,
+    .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
index d8099ff92e..88add0819a 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -64,9 +64,9 @@ typedef struct INIContext {
 #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},
+    { "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);
@@ -74,9 +74,9 @@ DEFINE_FORMATTER_CLASS(ini);
 static char *ini_escape_str(AVBPrint *dst, const char *src)
 {
     int i = 0;
-    char c = 0;
+    char c;
 
-    while (c = src[i++]) {
+    while ((c = src[i++])) {
         switch (c) {
         case '\b': av_bprintf(dst, "%s", "\\b"); break;
         case '\f': av_bprintf(dst, "%s", "\\f"); break;
@@ -84,9 +84,11 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
         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);
+        case '#':
+        case '=':
+        case ':':
+            av_bprint_chars(dst, '\\', 1);
+            /* fallthrough */
         default:
             if ((unsigned char)c < 32)
                 av_bprintf(dst, "\\x00%02x", c & 0xff);
@@ -112,23 +114,23 @@ static void ini_print_section_header(AVTextFormatContext *wctx, const void *data
         return;
     }
 
-    if (wctx->nb_item[wctx->level-1])
+    if (wctx->nb_item[wctx->level - 1])
         writer_w8(wctx, '\n');
 
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+    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))) {
+        !(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);
+            unsigned 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, ".%u", n);
         }
     }
 
-    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
         writer_printf(wctx, "[%s]\n", buf->str);
 }
 
@@ -154,6 +156,6 @@ const AVTextFormatter avtextformatter_ini = {
     .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,
+    .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
index c26a912435..b61d3740c6 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -56,9 +56,9 @@ typedef struct 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 },
+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 }
 };
 
@@ -76,8 +76,8 @@ static av_cold int json_init(AVTextFormatContext *wctx)
 
 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};
+    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++) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index 6c89d01e9d..befb39246d 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -58,11 +58,11 @@ typedef struct XMLContext {
 #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},
+    { "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);
@@ -104,8 +104,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 
         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 : "");
+                      xml->fully_qualified ? "ffprobe:" : "",
+                      xml->fully_qualified ? qual : "");
         return;
     }
 
@@ -115,12 +115,13 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
     }
 
     if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
-        wctx->level && wctx->nb_item[wctx->level-1])
+        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_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;
@@ -131,7 +132,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
         }
         writer_printf(wctx, ">\n", section->name);
     } else {
-        XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "<%s ", section->name);
         xml->within_tag = 1;
     }
 }
@@ -148,7 +150,8 @@ static void xml_print_section_footer(AVTextFormatContext *wctx)
         writer_put_str(wctx, "/>\n");
         xml->indent_level--;
     } else {
-        XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "</%s>\n", section->name);
         xml->indent_level--;
     }
 }
@@ -195,7 +198,8 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
     av_bprint_finalize(&buf, NULL);
 }
 
-static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
+static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
     xml_print_value(wctx, key, value, 0, 0);
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat: Quality improvements
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 01/10] fftools/textformat: Formatting and whitespace changes softworkz
@ 2025-04-16 10:12   ` softworkz
  2025-04-16 10:49     ` Andreas Rheinhardt
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 03/10] fftools/textformat: Introduce common header and deduplicate code softworkz
                     ` (9 subsequent siblings)
  11 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 111 +++++++++++++++++++-----------
 fftools/textformat/avtextformat.h |   6 +-
 fftools/textformat/tf_default.c   |   8 ++-
 fftools/textformat/tf_ini.c       |   2 +-
 fftools/textformat/tf_json.c      |   8 ++-
 fftools/textformat/tf_xml.c       |   3 -
 fftools/textformat/tw_avio.c      |   9 ++-
 7 files changed, 93 insertions(+), 54 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 811b14b999..4c8def8e65 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
 
 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++)
+    for (unsigned i = 0; i < ubuf_size; i++)
         av_bprintf(bp, "%02X", ubuf[i]);
 }
 
@@ -141,7 +140,10 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
     AVTextFormatContext *tctx;
     int i, ret = 0;
 
-    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
+    if (!ptctx || !formatter)
+        return AVERROR(EINVAL);
+
+    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
@@ -213,25 +215,26 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
                     av_log(NULL, AV_LOG_ERROR, " %s", n);
                 av_log(NULL, AV_LOG_ERROR, "\n");
             }
-            return ret;
+            goto fail;
         }
 
     /* validate replace string */
     {
-        const uint8_t *p = tctx->string_validation_replacement;
-        const uint8_t *endp = p + strlen(p);
+        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
+        const uint8_t *endp = p + strlen((const char *)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);
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
                 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;
+                av_bprint_finalize(&bp, NULL);
+                goto fail;
             }
         }
     }
@@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
 
 void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
+    if (section_id < 0 || section_id >= tctx->nb_sections)
+        return;
+
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
 
@@ -272,6 +278,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
 
 void avtext_print_section_footer(AVTextFormatContext *tctx)
 {
+    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
     int section_id = tctx->section[tctx->level]->id;
     int parent_section_id = tctx->level
         ? tctx->section[tctx->level - 1]->id
@@ -289,7 +298,12 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
+
+    if (!key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
+    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);
@@ -299,24 +313,25 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
 
 static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
 {
-    const uint8_t *p, *endp;
+    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
     AVBPrint dstbuf;
+    AVBPrint bp;
     int invalid_chars_nb = 0, ret = 0;
 
+    *dstp = NULL;
     av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
 
-    endp = src + strlen(src);
-    for (p = src; *p;) {
-        uint32_t code;
+    endp = srcp + strlen(src);
+    for (p = srcp; *p;) {
+        int32_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);
+            av_bprint_clear(&bp);
+            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;
         }
 
@@ -336,7 +351,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
         }
 
         if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
-            av_bprint_append_data(&dstbuf, p0, p-p0);
+            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
     }
 
     if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
@@ -346,6 +361,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
 end:
     av_bprint_finalize(&dstbuf, dstp);
+    av_bprint_finalize(&bp, NULL);
     return ret;
 }
 
@@ -358,17 +374,18 @@ struct unit_value {
     const char *unit;
 };
 
-static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
+static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
 {
     double vald;
-    int64_t vali;
+    int64_t vali = 0;
     int show_float = 0;
 
     if (uv.unit == unit_second_str) {
         vald = uv.val.d;
         show_float = 1;
     } else {
-        vald = vali = uv.val.i;
+        vald = (double)uv.val.i;
+        vali = uv.val.i;
     }
 
     if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
@@ -387,17 +404,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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);
+                index = (int64_t)(log2(vald) / 10);
+                index = av_clip64(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);
+                index = (int64_t)(log10(vald) / 3);
+                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
                 vald /= si_prefixes[index].dec_val;
                 prefix_string = si_prefixes[index].dec_str;
             }
-            vali = vald;
+            vali = (int64_t)vald;
         }
 
         if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
@@ -425,9 +442,14 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
 
 int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
     int ret = 0;
 
+    if (!key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return AVERROR(EINVAL);
+
+    section = tctx->section[tctx->level];
+
     if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
         (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
             && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
@@ -469,12 +491,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
 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);
+        char buf[128];
+        double d = av_q2d(*time_base) * ts;
         struct unit_value uv;
         uv.val.d = d;
         uv.unit = unit_second_str;
@@ -495,7 +516,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
                        const uint8_t *data, int size)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -522,25 +544,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 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 };
+    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
+    int len;
 
     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);
+    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
+    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
     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)
+                           uint8_t *data, int size, const char *format,
+                           int columns, int bytes, int offset_add)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
+
+    if (!name || !data || !format || columns <= 0 || bytes <= 0)
+        return;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -607,12 +633,15 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
     AVTextWriterContext *wctx;
     int ret = 0;
 
-    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
+    if (!pwctx || !writer)
+        return AVERROR(EINVAL);
+
+    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
-    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
+    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c598af3450..aea691f351 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -21,9 +21,7 @@
 #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"
@@ -103,7 +101,7 @@ struct AVTextFormatContext {
     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];
+    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
     AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
                                                   ///  used by various formatters
 
@@ -124,7 +122,7 @@ struct AVTextFormatContext {
 #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 2c5047eafd..ad97173b0b 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -68,9 +68,10 @@ 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;
+    unsigned i;
+
     for (i = 0; src[i] && i < dst_size - 1; i++)
-        dst[i] = av_toupper(src[i]);
+        dst[i] = (char)av_toupper(src[i]);
     dst[i] = 0;
     return dst;
 }
@@ -106,6 +107,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     const struct AVTextFormatSection *section = wctx->section[wctx->level];
     char buf[32];
 
+    if (!section)
+        return;
+
     if (def->noprint_wrappers || def->nested_section[wctx->level])
         return;
 
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index 88add0819a..dd77d0e8bf 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
             /* fallthrough */
         default:
             if ((unsigned char)c < 32)
-                av_bprintf(dst, "\\x00%02x", c & 0xff);
+                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
             else
                 av_bprint_chars(dst, c, 1);
             break;
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index b61d3740c6..e86cdbf5d9 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
     static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
     const char *p;
 
+    if (!src) {
+        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source string\n");
+        return NULL;
+    }
+
     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);
+            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
         } else {
             av_bprint_chars(dst, *p, 1);
         }
@@ -105,6 +110,7 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
         wctx->section[wctx->level-1] : NULL;
 
     if (wctx->level && wctx->nb_item[wctx->level-1])
+    if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
     if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index befb39246d..28abfc6400 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -18,10 +18,7 @@
  * 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"
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
index 6034f74ec9..b1743fb43a 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -53,7 +53,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
 static void io_put_str(AVTextWriterContext *wctx, const char *str)
 {
     IOWriterContext *ctx = wctx->priv;
-    avio_write(ctx->avio_context, str, strlen(str));
+    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
 static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
@@ -78,10 +78,12 @@ const AVTextWriter avtextwriter_avio = {
 
 int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
 {
+    if (!pwctx || !output_filename || !output_filename[0])
+        return AVERROR(EINVAL);
+
     IOWriterContext *ctx;
     int ret;
 
-
     ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
     if (ret < 0)
         return ret;
@@ -103,6 +105,9 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
 
 int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
 {
+    if (!pwctx || !avio_ctx)
+        return AVERROR(EINVAL);
+
     IOWriterContext *ctx;
     int 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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 03/10] fftools/textformat: Introduce common header and deduplicate code
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 01/10] fftools/textformat: Formatting and whitespace changes softworkz
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat: Quality improvements softworkz
@ 2025-04-16 10:12   ` softworkz
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 04/10] fftools/tf_internal: Use ac_default_item_name softworkz
                     ` (8 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextwriters.h |  2 +-
 fftools/textformat/tf_compact.c    | 32 ++++-------
 fftools/textformat/tf_default.c    | 27 +++-------
 fftools/textformat/tf_flat.c       | 25 +++------
 fftools/textformat/tf_ini.c        | 24 +++------
 fftools/textformat/tf_internal.h   | 85 ++++++++++++++++++++++++++++++
 fftools/textformat/tf_json.c       | 38 +++++--------
 fftools/textformat/tf_xml.c        | 35 +++++-------
 fftools/textformat/tw_avio.c       |  9 ++--
 fftools/textformat/tw_buffer.c     |  7 +--
 fftools/textformat/tw_stdout.c     |  8 +--
 11 files changed, 150 insertions(+), 142 deletions(-)
 create mode 100644 fftools/textformat/tf_internal.h

diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index 34db3f1832..fd6da747eb 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -41,7 +41,7 @@ typedef struct AVTextWriter {
     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, ...);
+    void (*writer_printf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
 } AVTextWriter;
 
 typedef struct AVTextWriterContext {
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index d4ac296a42..e52888239e 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -28,23 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 
 /* Compact output */
@@ -157,9 +141,12 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
+
     compact->terminate_line[wctx->level] = 1;
     compact->has_nested_elems[wctx->level] = 0;
 
@@ -210,8 +197,11 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
 
 static void compact_print_section_footer(AVTextFormatContext *wctx)
 {
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     CompactContext *compact = wctx->priv;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (!compact->nested_section[wctx->level] &&
         compact->terminate_line[wctx->level] &&
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index ad97173b0b..019bda9d44 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -27,21 +27,7 @@
 #include "avtextformat.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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -80,9 +66,11 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(&wctx->section_pbuf[wctx->level]);
     if (parent_section &&
@@ -104,7 +92,8 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 static void default_print_section_footer(AVTextFormatContext *wctx)
 {
     DefaultContext *def = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
     char buf[32];
 
     if (!section)
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index f692971bcc..d5517f109b 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -28,22 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
-
+#include "tf_internal.h"
 
 /* Flat output */
 
@@ -118,9 +103,11 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     /* build section header */
     av_bprint_clear(buf);
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index dd77d0e8bf..8959785295 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -28,21 +28,7 @@
 
 #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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -104,9 +90,11 @@ 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(buf);
     if (!parent_section) {
diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
new file mode 100644
index 0000000000..7b326328cb
--- /dev/null
+++ b/fftools/textformat/tf_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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
+ */
+
+/**
+ * @file
+ * Internal utilities for text formatters.
+ */
+
+#ifndef FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+#define FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+
+#include "avtextformat.h"
+
+#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                    \
+}
+
+
+/**
+ * Safely validate and access a section at a given level
+ */
+static inline const AVTextFormatSection *tf_get_section(AVTextFormatContext *tfc, int level)
+{
+    if (!tfc || level < 0 || level >= SECTION_MAX_NB_LEVELS || !tfc->section[level]) {
+        if (tfc)
+            av_log(tfc, AV_LOG_ERROR, "Invalid section access at level %d\n", level);
+        return NULL;
+    }
+    return tfc->section[level];
+}
+
+/**
+ * Safely access the parent section
+ */
+static inline const AVTextFormatSection *tf_get_parent_section(AVTextFormatContext *tfc, int level)
+{
+    if (level <= 0)
+        return NULL;
+
+    return tf_get_section(tfc, level - 1);
+}
+
+static inline void writer_w8(AVTextFormatContext *wctx, int b)
+{
+    wctx->writer->writer->writer_w8(wctx->writer, b);
+}
+
+static inline void writer_put_str(AVTextFormatContext *wctx, const char *str)
+{
+    wctx->writer->writer->writer_put_str(wctx->writer, str);
+}
+
+static inline void writer_printf(AVTextFormatContext *wctx, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    wctx->writer->writer->writer_printf(wctx->writer, fmt, args);
+    va_end(args);
+}
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_INTERNAL_H */
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index e86cdbf5d9..8072fa44a4 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -27,22 +27,7 @@
 #include "avtextformat.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                    \
-}
-
+#include "tf_internal.h"
 
 /* JSON output */
 
@@ -103,13 +88,14 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
 
 static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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])
+    if (!section)
+        return;
+
     if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
@@ -143,8 +129,11 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 
 static void json_print_section_footer(AVTextFormatContext *wctx)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         json->indent_level--;
@@ -177,9 +166,8 @@ static inline void json_print_item_str(AVTextFormatContext *wctx,
 
 static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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);
@@ -190,9 +178,8 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 
 static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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))
@@ -216,4 +203,3 @@ const AVTextFormatter avtextformatter_json = {
     .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
index 28abfc6400..6b09e09ab4 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -25,21 +25,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 /* XML output */
 
@@ -90,9 +76,11 @@ static av_cold int xml_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
@@ -138,7 +126,10 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 static void xml_print_section_footer(AVTextFormatContext *wctx)
 {
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
@@ -158,7 +149,10 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
 {
     AVBPrint buf;
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
 
@@ -216,4 +210,3 @@ const AVTextFormatter avtextformatter_xml = {
     .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
index b1743fb43a..48868ebf5d 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -56,14 +56,11 @@ static void io_put_str(AVTextWriterContext *wctx, const char *str)
     avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
-static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void io_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     IOWriterContext *ctx = wctx->priv;
-    va_list ap;
 
-    va_start(ap, fmt);
-    avio_vprintf(ctx->avio_context, fmt, ap);
-    va_end(ap);
+    avio_vprintf(ctx->avio_context, fmt, vl);
 }
 
 
@@ -78,7 +75,7 @@ const AVTextWriter avtextwriter_avio = {
 
 int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
 {
-    if (!pwctx || !output_filename || !output_filename[0])
+    if (!output_filename || !output_filename[0])
         return AVERROR(EINVAL);
 
     IOWriterContext *ctx;
diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
index f8b38414a6..f861722247 100644
--- a/fftools/textformat/tw_buffer.c
+++ b/fftools/textformat/tw_buffer.c
@@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
     av_bprintf(ctx->buffer, "%s", str);
 }
 
-static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     BufferWriterContext *ctx = wctx->priv;
 
-    va_list vargs;
-    va_start(vargs, fmt);
-    av_vbprintf(ctx->buffer, fmt, vargs);
-    va_end(vargs);
+    av_vbprintf(ctx->buffer, fmt, vl);
 }
 
 
diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
index 23de6f671f..dace55f38a 100644
--- a/fftools/textformat/tw_stdout.c
+++ b/fftools/textformat/tw_stdout.c
@@ -53,13 +53,9 @@ static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
     printf("%s", str);
 }
 
-static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
-    va_list ap;
-
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
+    vprintf(fmt, vl);
 }
 
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 04/10] fftools/tf_internal: Use ac_default_item_name
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
                     ` (2 preceding siblings ...)
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 03/10] fftools/textformat: Introduce common header and deduplicate code softworkz
@ 2025-04-16 10:12   ` softworkz
  2025-04-16 10:50     ` Andreas Rheinhardt
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 05/10] fftools/textformat: Add function avtext_print_integer_flags() softworkz
                     ` (7 subsequent siblings)
  11 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/tf_internal.h | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
index 7b326328cb..e145bc83bb 100644
--- a/fftools/textformat/tf_internal.h
+++ b/fftools/textformat/tf_internal.h
@@ -29,13 +29,9 @@
 #include "avtextformat.h"
 
 #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,                  \
+    .item_name  = av_default_item_name,             \
     .option     = name##_options                    \
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 05/10] fftools/textformat: Add function avtext_print_integer_flags()
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
                     ` (3 preceding siblings ...)
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 04/10] fftools/tf_internal: Use ac_default_item_name softworkz
@ 2025-04-16 10:12   ` softworkz
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 06/10] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
                     ` (6 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This function works analog to the avtext_print_string() which already
has a flags parameter.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 21 +++++++++++++++++++++
 fftools/textformat/avtextformat.h |  2 ++
 2 files changed, 23 insertions(+)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 4c8def8e65..d96ced9b7d 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -311,6 +311,27 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
     }
 }
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags)
+{
+    const AVTextFormatSection *section;
+
+    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
+    section = tctx->section[tctx->level];
+
+    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;
+
+    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, *srcp = (const uint8_t *)src;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index aea691f351..16cd9b214f 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -139,6 +139,8 @@ void avtext_print_section_footer(AVTextFormatContext *tctx);
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags);
+
 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);
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 06/10] fftools/ffmpeg_filter: Move some declaration to new header file
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
                     ` (4 preceding siblings ...)
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 05/10] fftools/textformat: Add function avtext_print_integer_flags() softworkz
@ 2025-04-16 10:12   ` softworkz
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 07/10] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
                     ` (5 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

to allow filtergraph printing to access the information.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_filter.c | 190 +-------------------------------
 fftools/ffmpeg_filter.h | 234 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+), 189 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index d314aec206..eab9487f97 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ffmpeg.h"
+#include "ffmpeg_filter.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -42,44 +43,6 @@
 // FIXME private header, used for mid_pred()
 #include "libavcodec/mathops.h"
 
-typedef struct FilterGraphPriv {
-    FilterGraph      fg;
-
-    // name used for logging
-    char             log_name[32];
-
-    int              is_simple;
-    // true when the filtergraph contains only meta filters
-    // that do not modify the frame data
-    int              is_meta;
-    // source filters are present in the graph
-    int              have_sources;
-    int              disable_conversions;
-
-    unsigned         nb_outputs_done;
-
-    const char      *graph_desc;
-
-    int              nb_threads;
-
-    // frame for temporarily holding output from the filtergraph
-    AVFrame         *frame;
-    // frame for sending output to the encoder
-    AVFrame         *frame_enc;
-
-    Scheduler       *sch;
-    unsigned         sch_idx;
-} FilterGraphPriv;
-
-static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
-{
-    return (FilterGraphPriv*)fg;
-}
-
-static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
-{
-    return (const FilterGraphPriv*)fg;
-}
 
 // data that is local to the filter thread and not visible outside of it
 typedef struct FilterGraphThread {
@@ -102,157 +65,6 @@ typedef struct FilterGraphThread {
     uint8_t         *eof_out;
 } FilterGraphThread;
 
-typedef struct InputFilterPriv {
-    InputFilter         ifilter;
-
-    InputFilterOptions  opts;
-
-    int                 index;
-
-    AVFilterContext    *filter;
-
-    // used to hold submitted input
-    AVFrame            *frame;
-
-    /* for filters that are not yet bound to an input stream,
-     * this stores the input linklabel, if any */
-    uint8_t            *linklabel;
-
-    // filter data type
-    enum AVMediaType    type;
-    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
-    // same as type otherwise
-    enum AVMediaType    type_src;
-
-    int                 eof;
-    int                 bound;
-    int                 drop_warned;
-    uint64_t            nb_dropped;
-
-    // parameters configured for this input
-    int                 format;
-
-    int                 width, height;
-    AVRational          sample_aspect_ratio;
-    enum AVColorSpace   color_space;
-    enum AVColorRange   color_range;
-
-    int                 sample_rate;
-    AVChannelLayout     ch_layout;
-
-    AVRational          time_base;
-
-    AVFrameSideData   **side_data;
-    int                 nb_side_data;
-
-    AVFifo             *frame_queue;
-
-    AVBufferRef        *hw_frames_ctx;
-
-    int                 displaymatrix_present;
-    int                 displaymatrix_applied;
-    int32_t             displaymatrix[9];
-
-    int                 downmixinfo_present;
-    AVDownmixInfo       downmixinfo;
-
-    struct {
-        AVFrame *frame;
-
-        int64_t last_pts;
-        int64_t end_pts;
-
-        /// marks if sub2video_update should force an initialization
-        unsigned int initialize;
-    } sub2video;
-} InputFilterPriv;
-
-static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
-{
-    return (InputFilterPriv*)ifilter;
-}
-
-typedef struct FPSConvContext {
-    AVFrame          *last_frame;
-    /* number of frames emitted by the video-encoding sync code */
-    int64_t           frame_number;
-    /* history of nb_frames_prev, i.e. the number of times the
-     * previous frame was duplicated by vsync code in recent
-     * do_video_out() calls */
-    int64_t           frames_prev_hist[3];
-
-    uint64_t          dup_warning;
-
-    int               last_dropped;
-    int               dropped_keyframe;
-
-    enum VideoSyncMethod vsync_method;
-
-    AVRational        framerate;
-    AVRational        framerate_max;
-    const AVRational *framerate_supported;
-    int               framerate_clip;
-} FPSConvContext;
-
-typedef struct OutputFilterPriv {
-    OutputFilter            ofilter;
-
-    int                     index;
-
-    void                   *log_parent;
-    char                    log_name[32];
-
-    char                   *name;
-
-    AVFilterContext        *filter;
-
-    /* desired output stream properties */
-    int                     format;
-    int                     width, height;
-    int                     sample_rate;
-    AVChannelLayout         ch_layout;
-    enum AVColorSpace       color_space;
-    enum AVColorRange       color_range;
-
-    AVFrameSideData       **side_data;
-    int                     nb_side_data;
-
-    // time base in which the output is sent to our downstream
-    // does not need to match the filtersink's timebase
-    AVRational              tb_out;
-    // at least one frame with the above timebase was sent
-    // to our downstream, so it cannot change anymore
-    int                     tb_out_locked;
-
-    AVRational              sample_aspect_ratio;
-
-    AVDictionary           *sws_opts;
-    AVDictionary           *swr_opts;
-
-    // those are only set if no format is specified and the encoder gives us multiple options
-    // They point directly to the relevant lists of the encoder.
-    const int              *formats;
-    const AVChannelLayout  *ch_layouts;
-    const int              *sample_rates;
-    const enum AVColorSpace *color_spaces;
-    const enum AVColorRange *color_ranges;
-
-    AVRational              enc_timebase;
-    int64_t                 trim_start_us;
-    int64_t                 trim_duration_us;
-    // offset for output timestamps, in AV_TIME_BASE_Q
-    int64_t                 ts_offset;
-    int64_t                 next_pts;
-    FPSConvContext          fps;
-
-    unsigned                flags;
-} OutputFilterPriv;
-
-static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
-{
-    return (OutputFilterPriv*)ofilter;
-}
-
 typedef struct FilterCommand {
     char *target;
     char *command;
diff --git a/fftools/ffmpeg_filter.h b/fftools/ffmpeg_filter.h
new file mode 100644
index 0000000000..94b94beece
--- /dev/null
+++ b/fftools/ffmpeg_filter.h
@@ -0,0 +1,234 @@
+/*
+ * 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_FFMPEG_FILTER_H
+#define FFTOOLS_FFMPEG_FILTER_H
+
+#include "ffmpeg.h"
+
+#include <stdint.h>
+
+#include "ffmpeg_sched.h"
+#include "sync_queue.h"
+
+#include "libavfilter/avfilter.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/dict.h"
+#include "libavutil/fifo.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/downmix_info.h"
+
+typedef struct FilterGraphPriv {
+    FilterGraph      fg;
+
+    // name used for logging
+    char             log_name[32];
+
+    int              is_simple;
+    // true when the filtergraph contains only meta filters
+    // that do not modify the frame data
+    int              is_meta;
+    // source filters are present in the graph
+    int              have_sources;
+    int              disable_conversions;
+
+    unsigned         nb_outputs_done;
+
+    const char      *graph_desc;
+
+    int              nb_threads;
+
+    // frame for temporarily holding output from the filtergraph
+    AVFrame         *frame;
+    // frame for sending output to the encoder
+    AVFrame         *frame_enc;
+
+    Scheduler       *sch;
+    unsigned         sch_idx;
+
+    AVBPrint graph_print_buf;
+
+} FilterGraphPriv;
+
+static inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
+{
+    return (FilterGraphPriv*)fg;
+}
+
+static inline const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
+{
+    return (const FilterGraphPriv*)fg;
+}
+
+typedef struct InputFilterPriv {
+    InputFilter         ifilter;
+
+    InputFilterOptions  opts;
+
+    int                 index;
+
+    AVFilterContext    *filter;
+
+    // used to hold submitted input
+    AVFrame            *frame;
+
+    /* for filters that are not yet bound to an input stream,
+     * this stores the input linklabel, if any */
+    uint8_t            *linklabel;
+
+    // filter data type
+    enum AVMediaType    type;
+    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
+    // same as type otherwise
+    enum AVMediaType    type_src;
+
+    int                 eof;
+    int                 bound;
+    int                 drop_warned;
+    uint64_t            nb_dropped;
+
+    // parameters configured for this input
+    int                 format;
+
+    int                 width, height;
+    AVRational          sample_aspect_ratio;
+    enum AVColorSpace   color_space;
+    enum AVColorRange   color_range;
+
+    int                 sample_rate;
+    AVChannelLayout     ch_layout;
+
+    AVRational          time_base;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
+    AVFifo             *frame_queue;
+
+    AVBufferRef        *hw_frames_ctx;
+
+    int                 displaymatrix_present;
+    int                 displaymatrix_applied;
+    int32_t             displaymatrix[9];
+
+    int                 downmixinfo_present;
+    AVDownmixInfo       downmixinfo;
+
+    struct {
+        AVFrame *frame;
+
+        int64_t last_pts;
+        int64_t end_pts;
+
+        /// marks if sub2video_update should force an initialization
+        unsigned int initialize;
+    } sub2video;
+} InputFilterPriv;
+
+static inline InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
+{
+    return (InputFilterPriv*)ifilter;
+}
+
+typedef struct FPSConvContext {
+    AVFrame          *last_frame;
+    /* number of frames emitted by the video-encoding sync code */
+    int64_t           frame_number;
+    /* history of nb_frames_prev, i.e. the number of times the
+     * previous frame was duplicated by vsync code in recent
+     * do_video_out() calls */
+    int64_t           frames_prev_hist[3];
+
+    uint64_t          dup_warning;
+
+    int               last_dropped;
+    int               dropped_keyframe;
+
+    enum VideoSyncMethod vsync_method;
+
+    AVRational        framerate;
+    AVRational        framerate_max;
+    const AVRational *framerate_supported;
+    int               framerate_clip;
+} FPSConvContext;
+
+
+typedef struct OutputFilterPriv {
+    OutputFilter            ofilter;
+
+    int                     index;
+
+    void                   *log_parent;
+    char                    log_name[32];
+
+    char                   *name;
+
+    AVFilterContext        *filter;
+
+    /* desired output stream properties */
+    int                     format;
+    int                     width, height;
+    int                     sample_rate;
+    AVChannelLayout         ch_layout;
+    enum AVColorSpace       color_space;
+    enum AVColorRange       color_range;
+
+    AVFrameSideData       **side_data;
+    int                     nb_side_data;
+
+    // time base in which the output is sent to our downstream
+    // does not need to match the filtersink's timebase
+    AVRational              tb_out;
+    // at least one frame with the above timebase was sent
+    // to our downstream, so it cannot change anymore
+    int                     tb_out_locked;
+
+    AVRational              sample_aspect_ratio;
+
+    AVDictionary           *sws_opts;
+    AVDictionary           *swr_opts;
+
+    // those are only set if no format is specified and the encoder gives us multiple options
+    // They point directly to the relevant lists of the encoder.
+    const int              *formats;
+    const AVChannelLayout  *ch_layouts;
+    const int              *sample_rates;
+    const enum AVColorSpace *color_spaces;
+    const enum AVColorRange *color_ranges;
+
+    AVRational              enc_timebase;
+    int64_t                 trim_start_us;
+    int64_t                 trim_duration_us;
+    // offset for output timestamps, in AV_TIME_BASE_Q
+    int64_t                 ts_offset;
+    int64_t                 next_pts;
+    FPSConvContext          fps;
+
+    unsigned                flags;
+} OutputFilterPriv;
+
+static inline OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
+{
+    return (OutputFilterPriv*)ofilter;
+}
+
+#endif /* FFTOOLS_FFMPEG_FILTER_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 07/10] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
                     ` (5 preceding siblings ...)
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 06/10] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-04-16 10:12   ` softworkz
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 08/10] fftools/resources: Add resource manager files softworkz
                     ` (4 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/APIchanges         |  3 +++
 libavfilter/avfilter.c |  9 +++++++++
 libavfilter/avfilter.h | 12 ++++++++++++
 3 files changed, 24 insertions(+)

diff --git a/doc/APIchanges b/doc/APIchanges
index 65bf5a9419..32374f5453 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
+  Add avfilter_link_get_hw_frames_ctx().
+
 2025-04-07 - 19e9a203b7 - lavu 60.01.100 - dict.h
   Add AV_DICT_DEDUP.
 
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index 64c1075c40..c76d43a215 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -989,6 +989,15 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx)
     return pads[pad_idx].type;
 }
 
+AVBufferRef *avfilter_link_get_hw_frames_ctx(AVFilterLink *link)
+{
+    FilterLink *plink = ff_filter_link(link);
+    if (plink->hw_frames_ctx)
+        return av_buffer_ref(plink->hw_frames_ctx);
+
+    return NULL;
+}
+
 static int default_filter_frame(AVFilterLink *link, AVFrame *frame)
 {
     return ff_filter_frame(link->dst->outputs[0], frame);
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index a89d3cf658..f85929dc5c 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -96,6 +96,18 @@ const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
  */
 enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
 
+/**
+ * Get the hardware frames context of a filter link.
+ *
+ * @param link an AVFilterLink
+ *
+ * @return a ref-counted copy of the link's hw_frames_ctx field if there is
+ *         a hardware frames context associated with the link or NULL otherwise.
+ *         The returned AVBufferRef needs to be released with av_buffer_unref()
+ *         when it is no longer used.
+ */
+AVBufferRef* avfilter_link_get_hw_frames_ctx(AVFilterLink *link);
+
 /**
  * Lists of formats / etc. supported by an end of a link.
  *
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 08/10] fftools/resources: Add resource manager files
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
                     ` (6 preceding siblings ...)
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 07/10] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
@ 2025-04-16 10:12   ` softworkz
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 09/10] fftools/graphprint: Add execution graph printing softworkz
                     ` (3 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 ffbuild/common.mak           |  28 ++-
 fftools/resources/.gitignore |   4 +
 fftools/resources/Makefile   |  27 +++
 fftools/resources/graph.css  | 353 +++++++++++++++++++++++++++++++++++
 fftools/resources/graph.html |  86 +++++++++
 fftools/resources/resman.c   | 213 +++++++++++++++++++++
 fftools/resources/resman.h   |  50 +++++
 7 files changed, 760 insertions(+), 1 deletion(-)
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h

diff --git a/ffbuild/common.mak b/ffbuild/common.mak
index ca45a0f368..eb68796001 100644
--- a/ffbuild/common.mak
+++ b/ffbuild/common.mak
@@ -139,6 +139,32 @@ else
 	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
 endif
 
+# 1) Preprocess CSS to a minified version
+%.css.min: %.css
+	# Must start with a tab in the real Makefile
+	sed 's!/\\*.*\\*/!!g' $< \
+	| tr '\n' ' ' \
+	| tr -s ' ' \
+	| sed 's/^ //; s/ $$//' \
+	> $@
+
+# 2) Gzip the minified CSS
+%.css.min.gz: %.css.min
+	$(M)gzip -nc9 $< > $@
+
+# 3) Convert the gzipped CSS to a .c array
+%.css.c: %.css.min.gz $(BIN2CEXE)
+	$(BIN2C) $< $@ $(subst .,_,$(basename $(notdir $@)))
+
+# 4) Gzip the HTML file (no minification needed)
+%.html.gz: TAG = GZIP
+%.html.gz: %.html
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) > $@
+
+# 5) Convert the gzipped HTML to a .c array
+%.html.c: %.html.gz $(BIN2CEXE)
+	$(BIN2C) $< $@ $(subst .,_,$(basename $(notdir $@)))
+
 clean::
 	$(RM) $(BIN2CEXE) $(CLEANSUFFIXES:%=ffbuild/%)
 
@@ -214,7 +240,7 @@ $(TOOLOBJS): | tools
 
 OUTDIRS := $(OUTDIRS) $(dir $(OBJS) $(HOBJS) $(HOSTOBJS) $(SLIBOBJS) $(SHLIBOBJS) $(STLIBOBJS) $(TESTOBJS))
 
-CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
+CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *.html.gz *.html.c *.css.gz *.css.c  *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
 LIBSUFFIXES       = *.a *.lib *.so *.so.* *.dylib *.dll *.def *.dll.a
 
 define RULES
diff --git a/fftools/resources/.gitignore b/fftools/resources/.gitignore
new file mode 100644
index 0000000000..5f496535a6
--- /dev/null
+++ b/fftools/resources/.gitignore
@@ -0,0 +1,4 @@
+*.html.c
+*.css.c
+*.html.gz
+*.css.gz
diff --git a/fftools/resources/Makefile b/fftools/resources/Makefile
new file mode 100644
index 0000000000..f3a0d0a970
--- /dev/null
+++ b/fftools/resources/Makefile
@@ -0,0 +1,27 @@
+clean::
+	$(RM) $(CLEANSUFFIXES:%=fftools/resources/%)
+
+
+HTML_RESOURCES := fftools/resources/graph.html \
+
+# .html => (gzip) .html.gz => (bin2c) .html.c => (cc) .o
+HTML_RESOURCES_GZ := $(HTML_RESOURCES:.html=.html.gz)
+HTML_RESOURCES_C := $(HTML_RESOURCES_GZ:.html.gz=.html.c)
+HTML_RESOURCES_OBJS := $(HTML_RESOURCES_C:.c=.o)
+
+CSS_RESOURCES := fftools/resources/graph.css   \
+
+# .css => (sh) .css.min  => (gzip) .css.min.gz => (bin2c) .css.c => (cc) .o
+CSS_RESOURCES_MIN := $(CSS_RESOURCES:.css=.css.min)
+CSS_RESOURCES_GZ := $(CSS_RESOURCES_MIN:.css.min=.css.min.gz)
+CSS_RESOURCES_C := $(CSS_RESOURCES_GZ:.css.min.gz=.css.c)
+CSS_RESOURCES_OBJS := $(CSS_RESOURCES_C:.c=.o)
+
+# Uncomment to prevent deletion
+#.PRECIOUS: %.css.c %.css.min %.css.gz  %.css.min.gz
+
+OBJS-resman +=                  \
+    fftools/resources/resman.o          \
+    $(HTML_RESOURCES_OBJS)      \
+    $(CSS_RESOURCES_OBJS)       \
+
diff --git a/fftools/resources/graph.css b/fftools/resources/graph.css
new file mode 100644
index 0000000000..ab480673ab
--- /dev/null
+++ b/fftools/resources/graph.css
@@ -0,0 +1,353 @@
+/* Variables */
+.root {
+    --ff-colvideo: #6eaa7b;
+    --ff-colaudio: #477fb3;
+    --ff-colsubtitle: #ad76ab;
+    --ff-coltext: #666;
+}
+
+/* Common & Misc */
+.ff-inputfiles rect, .ff-outputfiles rect, .ff-inputstreams rect, .ff-outputstreams rect, .ff-decoders rect, .ff-encoders rect {
+    stroke-width: 0;
+    stroke: transparent;
+    filter: none !important;
+    fill: transparent !important;
+    display: none !important;
+}
+
+.cluster span {
+    color: var(--ff-coltext);
+}
+
+.cluster rect {
+    stroke: #dfdfdf !important;
+    transform: translateY(-2.3rem);
+    filter: drop-shadow(1px 2px 2px rgba(185,185,185,0.2)) !important;
+    rx: 8;
+    ry: 8;
+}
+
+.cluster-label {
+    font-size: 1.1rem;
+}
+
+    .cluster-label .nodeLabel {
+        display: block;
+        font-weight: 500;
+        color: var(--ff-coltext);
+    }
+
+    .cluster-label div {
+        max-width: unset !important;
+        padding: 3px;
+    }
+
+    .cluster-label foreignObject {
+        transform: translateY(-0.7rem);
+    }
+
+/* Input and output files */
+.node.ff-inputfile .label foreignObject, .node.ff-outputfile .label foreignObject {
+    overflow: visible;
+}
+
+.cluster.ff-inputfile .cluster-label foreignObject div:not(foreignObject div div), .cluster.ff-outputfile .cluster-label foreignObject div:not(foreignObject div div) {
+    display: table !important;
+}
+
+.nodeLabel div.ff-inputfile, .nodeLabel div.ff-outputfile {
+    font-size: 1.1rem;
+    font-weight: 500;
+    min-width: 14rem;
+    width: 100%;
+    display: flex;
+    color: var(--ff-coltext);
+    margin-top: 0.1rem;
+    line-height: 1.35;
+    padding-bottom: 1.9rem;
+}
+
+.nodeLabel div.ff-outputfile {
+    flex-direction: row-reverse;
+}
+
+.ff-inputfile .index, .ff-outputfile .index {
+    order: 2;
+    color: var(--ff-coltext);
+    text-align: center;
+    border-radius: 0.45rem;
+    border: 0.18em solid #666666db;
+    font-weight: 600;
+    padding: 0 0.3em;
+    opacity: 0.8;
+}
+
+    .ff-inputfile .index::before {
+        content: 'In ';
+    }
+
+    .ff-outputfile .index::before {
+        content: 'Out ';
+    }
+
+.ff-inputfile .demuxer_name, .ff-outputfile .muxer_name {
+    flex: 1;
+    order: 1;
+    font-size: 0.9rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: center;
+    max-width: 8rem;
+    align-content: center;
+    margin: 0.2rem 0.4rem 0 0.4rem;
+}
+
+.ff-inputfile .file_extension, .ff-outputfile .file_extension {
+    order: 0;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.45rem;
+    font-weight: 600;
+    padding: 0 0.4em;
+    align-content: center;
+    opacity: 0.8;
+}
+
+.ff-inputfile .url, .ff-outputfile .url {
+    order: 4;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0.75rem;
+    font-size: 0.7rem;
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin: 0 0.3rem;
+    direction: rtl;
+    color: #999;
+}
+
+.cluster.ff-inputfile rect, .cluster.ff-outputfile rect {
+    transform: translateY(-1.8rem);
+    fill: url(#ff-radgradient);
+}
+
+/* Input and output streams */
+.node.ff-inputstream rect, .node.ff-outputstream rect {
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    fill: white;
+    stroke: #e5e5e5 !important;
+    height: 2.7rem;
+    transform: translateY(0.2rem);
+    filter: none;
+    rx: 3;
+    ry: 3;
+}
+
+.node.ff-inputstream .label foreignObject, .node.ff-outputstream .label foreignObject {
+    transform: translateY(-0.2%);
+    overflow: visible;
+}
+
+    .node.ff-inputstream .label foreignObject div:not(foreignObject div div), .node.ff-outputstream .label foreignObject div:not(foreignObject div div) {
+        display: block !important;
+        line-height: 1.5 !important;
+    }
+
+.nodeLabel div.ff-inputstream, .nodeLabel div.ff-outputstream {
+    font-size: 1.0rem;
+    font-weight: 500;
+    min-width: 12rem;
+    width: 100%;
+    display: flex;
+}
+
+.nodeLabel div.ff-outputstream {
+    flex-direction: row-reverse;
+}
+
+.ff-inputstream .name, .ff-outputstream .name {
+    flex: 1;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: left;
+    align-content: center;
+    margin-bottom: -0.15rem;
+}
+
+.ff-inputstream .index, .ff-outputstream .index {
+    flex: 0 0 1.4rem;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.3rem;
+    font-weight: 600;
+    margin-right: -0.3rem;
+    margin-left: 0.4rem;
+    opacity: 0.8;
+}
+
+.ff-outputstream .index {
+    margin-right: 0.6rem;
+    margin-left: -0.4rem;
+}
+
+.ff-inputstream::before, .ff-outputstream::before {
+    font-variant-emoji: text;
+    flex: 0 0 2rem;
+    margin-left: -0.8rem;
+    margin-right: 0.2rem;
+}
+
+.ff-outputstream::before {
+    margin-left: 0.2rem;
+    margin-right: -0.6rem;
+}
+
+.ff-inputstream.video::before, .ff-outputstream.video::before {
+    content: '\239A';
+    color: var(--ff-colvideo);
+    font-size: 2.25rem;
+    line-height: 0.5;
+    font-weight: bold;
+}
+
+.ff-inputstream.audio::before, .ff-outputstream.audio::before {
+    content: '\1F39D';
+    color: var(--ff-colaudio);
+    font-size: 1.75rem;
+    line-height: 0.9;
+}
+
+.ff-inputstream.subtitle::before, .ff-outputstream.subtitle::before {
+    content: '\1AC';
+    color: var(--ff-colsubtitle);
+    font-size: 1.2rem;
+    line-height: 1.1;
+    transform: scaleX(1.5);
+    margin-top: 0.050rem;
+}
+
+.ff-inputstream.attachment::before, .ff-outputstream.attachment::before {
+    content: '\1F4CE';
+    font-size: 1.3rem;
+    line-height: 1.15;
+}
+
+.ff-inputstream.data::before, .ff-outputstream.data::before {
+    content: '\27E8\2219\2219\2219\27E9';
+    font-size: 1.15rem;
+    line-height: 1.17;
+    letter-spacing: -0.3px;
+}
+
+/* Filter Graphs */
+.cluster.ff-filters rect {
+    stroke-dasharray: 6 !important;
+    stroke-width: 1.3px;
+    stroke: #d1d1d1 !important;
+    filter: none !important;
+}
+
+.cluster.ff-filters div.ff-filters .id {
+    display: none;
+}
+
+.cluster.ff-filters div.ff-filters .name {
+    margin-right: 0.5rem;
+    font-size: 0.9rem;
+}
+
+.cluster.ff-filters div.ff-filters .description {
+    font-weight: 400;
+    font-size: 0.75rem;
+    vertical-align: middle;
+    color: #777;
+    font-family: Cascadia Code, Lucida Console, monospace;
+}
+
+/* Filter Shapes */
+.node.ff-filter rect {
+    rx: 10;
+    ry: 10;
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.node.ff-filter .label foreignObject {
+    transform: translateY(-0.4rem);
+    overflow: visible;
+}
+
+.nodeLabel div.ff-filter {
+    font-size: 1.0rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    min-width: 5.5rem;
+    margin-bottom: 0.5rem;
+}
+
+    .nodeLabel div.ff-filter span {
+        color: inherit;
+    }
+
+/* Decoders & Encoders */
+.node.ff-decoder rect, .node.ff-encoder rect {
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.nodeLabel div.ff-decoder, .nodeLabel div.ff-encoder {
+    font-size: 0.85rem;
+    font-weight: 500;
+    min-width: 3.5rem;
+}
+
+/* Links and Arrows */
+path.flowchart-link[id|='video'] {
+    stroke: var(--ff-colvideo);
+}
+
+path.flowchart-link[id|='audio'] {
+    stroke: var(--ff-colaudio);
+}
+
+path.flowchart-link[id|='subtitle'] {
+    stroke: var(--ff-colsubtitle);
+}
+
+marker.marker path {
+    fill: context-stroke;
+}
+
+.edgeLabel foreignObject {
+    transform: translateY(-1rem);
+}
+
+.edgeLabel p {
+    background: transparent;
+    white-space: nowrap;
+    margin: 1rem 0.5rem !important;
+    font-weight: 500;
+    color: var(--ff-coltext);
+}
+
+.edgeLabel, .labelBkg {
+    background: transparent;
+}
+
+.edgeLabels .edgeLabel * {
+    font-size: 0.8rem;
+}
diff --git a/fftools/resources/graph.html b/fftools/resources/graph.html
new file mode 100644
index 0000000000..cd94276fd4
--- /dev/null
+++ b/fftools/resources/graph.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <title>FFmpeg Graph</title>
+</head>
+<body>
+<style>
+    html {
+        color: #666;
+        font-family: Roboto;
+        height: 100%;
+    }
+
+    body {
+        background-color: #f9f9f9;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        margin: 0;
+        padding: 1.7rem 1.7rem 3.5rem 1.7rem;
+    }
+
+    div#banner {
+        align-items: center;
+        display: flex;
+        flex-direction: row;
+        margin-bottom: 1.5rem;
+        margin-left: 0.6vw;
+    }
+
+    div#header {
+        aspect-ratio: 1/1;
+        background-image: url(https://trac.ffmpeg.org/ffmpeg-logo.png);
+        background-size: cover;
+        width: 1.6rem;
+    }
+
+    h1 {
+        font-size: 1.2rem;
+        margin: 0 0.5rem;
+    }
+
+    pre.mermaid {
+        align-items: center;
+        background-color: white;
+        box-shadow: 2px 2px 25px 0px #00000010;
+        color: transparent;
+        display: flex;
+        flex: 1;
+        justify-content: center;
+        margin: 0;
+        overflow: overlay;
+    }
+
+    pre.mermaid svg {
+        height: auto;
+        margin: 0;
+        max-width: unset !important;
+        width: auto;
+    }
+
+    pre.mermaid svg * {
+        user-select: none;
+    }
+</style>
+<div id="banner">
+    <div id="header"></div>
+    <h1>FFmpeg Execution Graph</h1>
+</div>
+<pre class="mermaid">
+__###__
+</pre>
+<script type="module">
+        import vanillaJsWheelZoom from 'https://cdn.jsdelivr.net/npm/vanilla-js-wheel-zoom@9.0.4/+esm';
+        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
+        function initViewer() {
+            var element = document.querySelector('.mermaid svg')
+            vanillaJsWheelZoom.create('pre.mermaid svg', { type: 'html', smoothTimeDrag: 0, width: element.clientWidth, height: element.clientHeight, maxScale: 3 });
+        }
+        mermaid.initialize({ startOnLoad: false }); 
+        document.fonts.ready.then(() => { mermaid.run({ querySelector: '.mermaid', postRenderCallback: initViewer }); });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/fftools/resources/resman.c b/fftools/resources/resman.c
new file mode 100644
index 0000000000..488aaeecf6
--- /dev/null
+++ b/fftools/resources/resman.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <zlib.h>
+#include "resman.h"
+#include <libavformat/url.h>
+#include "fftools/ffmpeg_filter.h"
+#include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+
+extern const unsigned char ff_graph_html_data[];
+extern const unsigned int ff_graph_html_len;
+
+extern const unsigned char ff_graph_css_data[];
+extern const unsigned ff_graph_css_len;
+
+static const FFResourceDefinition resource_definitions[] = {
+    [FF_RESOURCE_GRAPH_CSS]   = { FF_RESOURCE_GRAPH_CSS,   "graph.css",   &ff_graph_css_data[0],   &ff_graph_css_len   },
+    [FF_RESOURCE_GRAPH_HTML]  = { FF_RESOURCE_GRAPH_HTML,  "graph.html",  &ff_graph_html_data[0],  &ff_graph_html_len  },
+};
+
+
+static const AVClass resman_class = {
+    .class_name = "ResourceManager",
+};
+
+typedef struct ResourceManagerContext {
+    const AVClass *class;
+    AVDictionary *resource_dic;
+} ResourceManagerContext;
+
+static AVMutex mutex = AV_MUTEX_INITIALIZER;
+
+ResourceManagerContext *resman_ctx = NULL;
+
+
+static int decompress_gzip(ResourceManagerContext *ctx, uint8_t *in, unsigned in_len, char **out, size_t *out_len)
+{
+    z_stream strm;
+    unsigned chunk = 65534;
+    int ret;
+    uint8_t *buf;
+
+    *out = NULL;
+    memset(&strm, 0, sizeof(strm));
+
+    // Allocate output buffer with extra byte for null termination
+    buf = (uint8_t *)av_mallocz(chunk + 1);
+    if (!buf) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to allocate decompression buffer\n");
+        return AVERROR(ENOMEM);
+    }
+
+    // 15 + 16 tells zlib to detect GZIP or zlib automatically
+    ret = inflateInit2(&strm, 15 + 16);
+    if (ret != Z_OK) {
+        av_log(ctx, AV_LOG_ERROR, "Error during zlib initialization: %s\n", strm.msg);
+        av_free(buf);
+        return AVERROR(ENOSYS);
+    }
+
+    strm.avail_in  = in_len;
+    strm.next_in   = in;
+    strm.avail_out = chunk;
+    strm.next_out  = buf;
+
+    ret = inflate(&strm, Z_FINISH);
+    if (ret != Z_OK && ret != Z_STREAM_END) {
+        av_log(ctx, AV_LOG_ERROR, "Inflate failed: %d, %s\n", ret, strm.msg);
+        inflateEnd(&strm);
+        av_free(buf);
+        return (ret == Z_STREAM_END) ? Z_OK : ((ret == Z_OK) ? Z_BUF_ERROR : ret);
+    }
+
+    if (strm.avail_out == 0) {
+        // TODO: Error or loop decoding?
+        av_log(ctx, AV_LOG_WARNING, "Decompression buffer may be too small\n");
+    }
+
+    *out_len = chunk - strm.avail_out;
+    buf[*out_len] = 0; // Ensure null termination
+
+    inflateEnd(&strm);
+    *out = (char *)buf;
+    return Z_OK;
+}
+
+static ResourceManagerContext *get_resman_context(void)
+{
+    ResourceManagerContext *res = resman_ctx;
+
+    ff_mutex_lock(&mutex);
+
+    if (res)
+        goto end;
+
+    res = av_mallocz(sizeof(ResourceManagerContext));
+    if (!res) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to allocate resource manager context\n");
+        goto end;
+    }
+
+    res->class = &resman_class;
+    resman_ctx = res;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
+
+
+void ff_resman_uninit(void)
+{
+    ff_mutex_lock(&mutex);
+
+    if (resman_ctx) {
+        if (resman_ctx->resource_dic)
+            av_dict_free(&resman_ctx->resource_dic);
+        av_freep(&resman_ctx);
+    }
+
+    ff_mutex_unlock(&mutex);
+}
+
+
+char *ff_resman_get_string(FFResourceId resource_id)
+{
+    ResourceManagerContext *ctx               = get_resman_context();
+    FFResourceDefinition resource_definition = { 0 };
+    AVDictionaryEntry *dic_entry;
+    char *res = NULL;
+
+    if (!ctx)
+        return NULL;
+
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(resource_definitions); ++i) {
+        FFResourceDefinition def = resource_definitions[i];
+        if (def.resource_id == resource_id) {
+            resource_definition = def;
+            break;
+        }
+    }
+
+    if (!resource_definition.name) {
+        av_log(ctx, AV_LOG_ERROR, "Unable to find resource with ID %d\n", resource_id);
+        return NULL;
+    }
+
+    ff_mutex_lock(&mutex);
+
+    dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+    if (!dic_entry) {
+        char *out = NULL;
+        size_t out_len;
+        int dict_ret;
+
+        int ret = decompress_gzip(ctx, (uint8_t *)resource_definition.data, *resource_definition.data_len, &out, &out_len);
+
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to decompress the resource with ID %d\n", resource_id);
+            goto end;
+        }
+
+        dict_ret = av_dict_set(&ctx->resource_dic, resource_definition.name, out, 0);
+        if (dict_ret < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to store decompressed resource in dictionary: %d\n", dict_ret);
+            av_freep(&out);
+            goto end;
+        }
+
+        av_freep(&out);
+        dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+        if (!dic_entry) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to retrieve resource from dictionary after storing it\n");
+            goto end;
+        }
+    }
+
+    res = dic_entry->value;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
diff --git a/fftools/resources/resman.h b/fftools/resources/resman.h
new file mode 100644
index 0000000000..6485db5091
--- /dev/null
+++ b/fftools/resources/resman.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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_RESOURCES_RESMAN_H
+#define FFTOOLS_RESOURCES_RESMAN_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "fftools/ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+#include "fftools/textformat/avtextformat.h"
+
+typedef enum {
+    FF_RESOURCE_GRAPH_CSS,
+    FF_RESOURCE_GRAPH_HTML,
+} FFResourceId;
+
+typedef struct FFResourceDefinition {
+    FFResourceId resource_id;
+    const char *name;
+
+    const unsigned char *data;
+    const unsigned *data_len;
+
+} FFResourceDefinition;
+
+void ff_resman_uninit(void);
+
+char *ff_resman_get_string(FFResourceId resource_id);
+
+#endif /* FFTOOLS_RESOURCES_RESMAN_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 09/10] fftools/graphprint: Add execution graph printing
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
                     ` (7 preceding siblings ...)
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 08/10] fftools/resources: Add resource manager files softworkz
@ 2025-04-16 10:12   ` softworkz
  2025-04-17 18:41     ` Michael Niedermayer
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 10/10] fftools/graphprint: Now, make it a Killer-Feature! softworkz
                     ` (2 subsequent siblings)
  11 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

The key benefits are:

- Different to other graph printing methods, this is outputting:
  - all graphs with runtime state
    (including auto-inserted filters)
  - each graph with its inputs and outputs
  - all filters with their in- and output pads
  - all connections between all input- and output pads
  - for each connection:
    - the runtime-negotiated format and media type
    - the hw context
    - if video hw context, both: hw pixfmt + sw pixfmt
- Output can either be printed to stdout or written to specified file
- Output is machine-readable
- Use the same output implementation as ffprobe, supporting multiple
  formats

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi                   |   10 +
 fftools/Makefile                  |   17 +
 fftools/ffmpeg.c                  |    4 +
 fftools/ffmpeg.h                  |    3 +
 fftools/ffmpeg_filter.c           |    5 +
 fftools/ffmpeg_opt.c              |   13 +
 fftools/graph/graphprint.c        | 1102 +++++++++++++++++++++++++++++
 fftools/graph/graphprint.h        |   30 +
 fftools/textformat/avtextformat.c |    2 +
 fftools/textformat/avtextformat.h |   29 +
 fftools/textformat/tf_mermaid.c   |  655 +++++++++++++++++
 fftools/textformat/tf_mermaid.h   |   41 ++
 12 files changed, 1911 insertions(+)
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 17ba876ea3..35675b5309 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1394,6 +1394,16 @@ It is on by default, to explicitly disable it you need to specify @code{-nostats
 @item -stats_period @var{time} (@emph{global})
 Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
 
+@item -print_graphs (@emph{global})
+Prints execution graph details to stderr in the format set via -print_graphs_format.
+
+@item -print_graphs_file @var{filename} (@emph{global})
+Writes execution graph details to the specified file in the format set via -print_graphs_format.
+
+@item -print_graphs_format @var{format} (@emph{global})
+Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
+The default format is json.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index e9c9891c34..8d87ea8255 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -9,6 +9,8 @@ AVBASENAMES  = ffmpeg ffplay ffprobe
 ALLAVPROGS   = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
 ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
 
+include $(SRC_PATH)/fftools/resources/Makefile
+
 OBJS-ffmpeg +=                  \
     fftools/ffmpeg_dec.o        \
     fftools/ffmpeg_demux.o      \
@@ -19,8 +21,21 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
+    fftools/graph/graphprint.o        \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
+    fftools/textformat/avtextformat.o \
+    fftools/textformat/tf_compact.o   \
+    fftools/textformat/tf_default.o   \
+    fftools/textformat/tf_flat.o      \
+    fftools/textformat/tf_ini.o       \
+    fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
+    fftools/textformat/tf_xml.o       \
+    fftools/textformat/tw_avio.o      \
+    fftools/textformat/tw_buffer.o    \
+    fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffprobe +=                       \
     fftools/textformat/avtextformat.o \
@@ -29,10 +44,12 @@ OBJS-ffprobe +=                       \
     fftools/textformat/tf_flat.o      \
     fftools/textformat/tf_ini.o       \
     fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
     fftools/textformat/tf_xml.o       \
     fftools/textformat/tw_avio.o      \
     fftools/textformat/tw_buffer.o    \
     fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffplay += fftools/ffplay_renderer.o
 
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..6766ec209c 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -81,6 +81,7 @@
 #include "ffmpeg.h"
 #include "ffmpeg_sched.h"
 #include "ffmpeg_utils.h"
+#include "graph/graphprint.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
+    if (print_graphs || print_graphs_file)
+        print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
+
     if (do_benchmark) {
         int64_t maxrss = getmaxrss() / 1024;
         av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 5869979214..7fbf0ad532 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -717,6 +717,9 @@ extern float max_error_rate;
 extern char *filter_nbthreads;
 extern int filter_complex_nbthreads;
 extern int vstats_version;
+extern int print_graphs;
+extern char *print_graphs_file;
+extern char *print_graphs_format;
 extern int auto_conversion_filters;
 
 extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index eab9487f97..b774606562 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -22,6 +22,7 @@
 
 #include "ffmpeg.h"
 #include "ffmpeg_filter.h"
+#include "graph/graphprint.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -2983,6 +2984,10 @@ read_frames:
     }
 
 finish:
+
+    if (print_graphs || print_graphs_file)
+        print_filtergraph(fg, fgt.graph);
+
     // EOF is normal termination
     if (ret == AVERROR_EOF)
         ret = 0;
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 6ec325f51e..3d1efe32f9 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -47,6 +47,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/stereo3d.h"
+#include "graph/graphprint.h"
 
 HWDevice *filter_hw_device;
 
@@ -75,6 +76,9 @@ float max_error_rate  = 2.0/3;
 char *filter_nbthreads;
 int filter_complex_nbthreads = 0;
 int vstats_version = 2;
+int print_graphs = 0;
+char *print_graphs_file = NULL;
+char *print_graphs_format = NULL;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1735,6 +1739,15 @@ const OptionDef options[] = {
         { .func_arg = opt_filter_complex_script },
         "deprecated, use -/filter_complex instead", "filename" },
 #endif
+    { "print_graphs",   OPT_TYPE_BOOL, 0,
+        { &print_graphs },
+        "print execution graph data to stderr" },
+    { "print_graphs_file", OPT_TYPE_STRING, 0,
+        { &print_graphs_file },
+        "write execution graph data to the specified file", "filename" },
+    { "print_graphs_format", OPT_TYPE_STRING, 0,
+        { &print_graphs_format },
+      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
new file mode 100644
index 0000000000..89c38d2e36
--- /dev/null
+++ b/fftools/graph/graphprint.c
@@ -0,0 +1,1102 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include <string.h>
+#include <stdatomic.h>
+
+#include "graphprint.h"
+
+#include <libavformat/url.h>
+
+#include "fftools/ffmpeg_filter.h"
+#include "fftools/ffmpeg_mux.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+#include "fftools/textformat/avtextformat.h"
+#include "fftools/textformat/tf_mermaid.h"
+#include "fftools/resources/resman.h"
+
+typedef enum {
+    SECTION_ID_ROOT,
+    SECTION_ID_FILTERGRAPHS,
+    SECTION_ID_FILTERGRAPH,
+    SECTION_ID_GRAPH_INPUTS,
+    SECTION_ID_GRAPH_INPUT,
+    SECTION_ID_GRAPH_OUTPUTS,
+    SECTION_ID_GRAPH_OUTPUT,
+    SECTION_ID_FILTERS,
+    SECTION_ID_FILTER,
+    SECTION_ID_FILTER_INPUTS,
+    SECTION_ID_FILTER_INPUT,
+    SECTION_ID_FILTER_OUTPUTS,
+    SECTION_ID_FILTER_OUTPUT,
+    SECTION_ID_HWFRAMESCONTEXT,
+    SECTION_ID_INPUTFILES,
+    SECTION_ID_INPUTFILE,
+    SECTION_ID_INPUTSTREAMS,
+    SECTION_ID_INPUTSTREAM,
+    SECTION_ID_OUTPUTFILES,
+    SECTION_ID_OUTPUTFILE,
+    SECTION_ID_OUTPUTSTREAMS,
+    SECTION_ID_OUTPUTSTREAM,
+    SECTION_ID_STREAMLINKS,
+    SECTION_ID_STREAMLINK,
+    SECTION_ID_DECODERS,
+    SECTION_ID_DECODER,
+    SECTION_ID_ENCODERS,
+    SECTION_ID_ENCODER,
+} SectionID;
+
+static struct AVTextFormatSection sections[] = {
+    [SECTION_ID_ROOT]            = { SECTION_ID_ROOT, "root", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER, { SECTION_ID_FILTERGRAPHS, SECTION_ID_INPUTFILES, SECTION_ID_OUTPUTFILES, SECTION_ID_DECODERS, SECTION_ID_ENCODERS, SECTION_ID_STREAMLINKS, -1 } },
+
+    [SECTION_ID_FILTERGRAPHS]    = { SECTION_ID_FILTERGRAPHS, "graphs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
+    [SECTION_ID_FILTERGRAPH]     = { SECTION_ID_FILTERGRAPH, "graph", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { SECTION_ID_GRAPH_INPUTS, SECTION_ID_GRAPH_OUTPUTS, SECTION_ID_FILTERS, -1 }, .element_name = "graph_info" },
+
+    [SECTION_ID_GRAPH_INPUTS]    = { SECTION_ID_GRAPH_INPUTS, "graph_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_INPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_INPUT]     = { SECTION_ID_GRAPH_INPUT, "graph_input", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_GRAPH_OUTPUTS]   = { SECTION_ID_GRAPH_OUTPUTS, "graph_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_OUTPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_OUTPUT]    = { SECTION_ID_GRAPH_OUTPUT, "graph_output", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTERS]         = { SECTION_ID_FILTERS, "filters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_FILTER, -1 }, .id_key = "graph_id" },
+    [SECTION_ID_FILTER]          = { SECTION_ID_FILTER, "filter", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { SECTION_ID_FILTER_INPUTS, SECTION_ID_FILTER_OUTPUTS, -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_INPUTS]   = { SECTION_ID_FILTER_INPUTS, "filter_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_INPUT, -1 } },
+    [SECTION_ID_FILTER_INPUT]    = { SECTION_ID_FILTER_INPUT, "filter_input", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "source_filter_id", .dest_id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_OUTPUTS]  = { SECTION_ID_FILTER_OUTPUTS, "filter_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_OUTPUT, -1 } },
+    [SECTION_ID_FILTER_OUTPUT]   = { SECTION_ID_FILTER_OUTPUT, "filter_output", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "filter_id", .dest_id_key = "dest_filter_id" },
+
+    [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context",  0, { -1 }, },
+
+    [SECTION_ID_INPUTFILES]      = { SECTION_ID_INPUTFILES, "inputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTFILE]       = { SECTION_ID_INPUTFILE, "inputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_INPUTSTREAMS]    = { SECTION_ID_INPUTSTREAMS, "inputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTSTREAM]     = { SECTION_ID_INPUTSTREAM, "inputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTFILES]     = { SECTION_ID_OUTPUTFILES, "outputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTFILE]      = { SECTION_ID_OUTPUTFILE, "outputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTSTREAMS]   = { SECTION_ID_OUTPUTSTREAMS, "outputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTSTREAM]    = { SECTION_ID_OUTPUTSTREAM, "outputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id", },
+
+    [SECTION_ID_STREAMLINKS]     = { SECTION_ID_STREAMLINKS, "streamlinks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAMLINK, -1 } },
+    [SECTION_ID_STREAMLINK]      = { SECTION_ID_STREAMLINK, "streamlink", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .src_id_key = "source_stream_id", .dest_id_key = "dest_stream_id" },
+
+    [SECTION_ID_DECODERS]        = { SECTION_ID_DECODERS, "decoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_DECODER, -1 } },
+    [SECTION_ID_DECODER]         = { SECTION_ID_DECODER, "decoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "source_id", .dest_id_key = "id" },
+
+    [SECTION_ID_ENCODERS]        = { SECTION_ID_ENCODERS, "encoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_ENCODER, -1 } },
+    [SECTION_ID_ENCODER]         = { SECTION_ID_ENCODER, "encoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "id", .dest_id_key = "dest_id" },
+};
+
+typedef struct GraphPrintContext {
+    AVTextFormatContext *tfc;
+    AVTextWriterContext *wctx;
+    AVDiagramConfig diagram_config;
+
+    int id_prefix_num;
+    int is_diagram;
+    int opt_flags;
+    int skip_buffer_filters;
+    AVBPrint pbuf;
+
+} GraphPrintContext;
+
+/* Text Format API Shortcuts */
+#define print_id(k, v)          print_sanizied_id(gpc, k, v, 0)
+#define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
+#define print_int(k, v)         avtext_print_integer(tfc, k, v)
+#define print_int_opt(k, v)     avtext_print_integer_flags(tfc, k, v, gpc->opt_flags)
+#define print_q(k, v, s)        avtext_print_rational(tfc, k, v, s)
+#define print_str(k, v)         avtext_print_string(tfc, k, v, 0)
+#define print_str_opt(k, v)     avtext_print_string(tfc, k, v, gpc->opt_flags)
+#define print_val(k, v, u)      avtext_print_unit_int(tfc, k, v, u)
+
+#define print_fmt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, 0);    \
+} while (0)
+
+#define print_fmt_opt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, gpc->opt_flags);    \
+} while (0)
+
+
+static atomic_int prefix_num = 0;
+
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    unsigned i;
+    for (i = 0; src[i] && i < dst_size - 1; i++)
+        dst[i]      = (char)av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static char *get_extension(const char *url)
+{
+    const char *ext;
+    URLComponents uc;
+    int ret;
+    char scratchpad[128];
+
+    if (!url)
+        return 0;
+
+    ret = ff_url_decompose(&uc, url, NULL);
+    if (ret < 0)
+        return NULL;
+    for (ext = uc.query; *ext != '.' && ext > uc.path; ext--) {
+    }
+
+    if (*ext != '.')
+        return 0;
+    if (uc.query - ext > sizeof(scratchpad))
+        return NULL; //not enough memory in our scratchpad
+    av_strlcpy(scratchpad, ext + 1, uc.query - ext);
+
+    return av_strdup(scratchpad);
+}
+
+static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+
+    if (!hw_device_context)
+        return;
+
+    print_int_opt("has_hw_device_context", 1);
+    print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
+}
+
+static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    const AVPixFmtDescriptor *pix_desc_hw;
+    const AVPixFmtDescriptor *pix_desc_sw;
+
+    if (!hw_frames_context || !hw_frames_context->device_ctx)
+        return;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT);
+
+    print_int_opt("has_hw_frames_context", 1);
+    print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
+
+    pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
+    if (pix_desc_hw) {
+        print_str("hw_pixel_format", pix_desc_hw->name);
+        if (pix_desc_hw->alias)
+            print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
+    }
+
+    pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
+    if (pix_desc_sw) {
+        print_str("sw_pixel_format", pix_desc_sw->name);
+        if (pix_desc_sw->alias)
+            print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
+    }
+
+    print_int_opt("width", hw_frames_context->width);
+    print_int_opt("height", hw_frames_context->height);
+    print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBufferRef *hw_frames_ctx;
+    char layout_string[64];
+
+    if (!link)
+        return;
+
+    hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
+
+    print_str_opt("media_type", av_get_media_type_string(link->type));
+
+    switch (link->type) {
+    case AVMEDIA_TYPE_VIDEO:
+
+        if (hw_frames_ctx && hw_frames_ctx->data) {
+            AVHWFramesContext *      hwfctx      = (AVHWFramesContext *)hw_frames_ctx->data;
+            const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
+            const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
+            if (pix_desc_hw && pix_desc_sw)
+                print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
+        } else {
+            print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
+        }
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        print_q("sar", link->sample_aspect_ratio, ':');
+
+        if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
+            print_str_opt("color_range", av_color_range_name(link->color_range));
+
+        if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
+            print_str("color_space", av_color_space_name(link->colorspace));
+        break;
+
+    case AVMEDIA_TYPE_SUBTITLE:
+        ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        break;
+
+    case AVMEDIA_TYPE_AUDIO:
+        av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
+        print_str("channel_layout", layout_string);
+        print_val("channels", link->ch_layout.nb_channels, "ch");
+        if (tfc->show_value_unit)
+            print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
+        else
+            print_val("sample_rate", link->sample_rate, "Hz");
+
+        break;
+    }
+
+    print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
+
+    if (hw_frames_ctx && hw_frames_ctx->data)
+        print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
+}
+
+static char sanitize_char(const char c)
+{
+    if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+        return c;
+    return '_';
+}
+
+static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBPrint buf;
+
+    if (!key || !id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    print_str(key, buf.str);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+    AVBPrint buf;
+
+    if (!id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    sec_ctx.context_id = buf.str;
+
+    avtext_print_section_header(tfc, &sec_ctx, section_id);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static const char *get_filterpad_name(const AVFilterPad *pad)
+{
+    return pad ? avfilter_pad_get_name(pad, 0) : "pad";
+}
+
+static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
+
+    ////print_id("filter_id", filter->name);
+
+    if (filter->filter) {
+        print_str("filter_name", filter->filter->name);
+        print_str_opt("description", filter->filter->description);
+        print_int_opt("nb_inputs", filter->nb_inputs);
+        print_int_opt("nb_outputs", filter->nb_outputs);
+    }
+
+    if (filter->hw_device_ctx) {
+        AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
+        print_hwdevicecontext(gpc, device_context);
+        if (filter->extra_hw_frames > 0)
+            print_int("extra_hw_frames", filter->extra_hw_frames);
+    }
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
+
+    for (unsigned i = 0; i < filter->nb_inputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->inputs[i];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
+        sec_ctx.context_type = NULL;
+
+        print_int_opt("input_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
+
+        dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
+        if (dic_entry) {
+            char buf[256];
+            (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
+            print_id_noprefix("source_filter_id", buf);
+        } else {
+            print_id("source_filter_id", link->src->name);
+        }
+
+        print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
+
+    for (unsigned i = 0; i < filter->nb_outputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->outputs[i];
+        char buf[256];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
+        sec_ctx.context_type = NULL;
+
+        dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
+        if (dic_entry) {
+            (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
+            print_id_noprefix("dest_filter_id", buf);
+        } else {
+            print_id("dest_filter_id", link->dst->name);
+        }
+
+        print_int_opt("output_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->srcpad));
+        ////print_id("dest_filter_id", link->dst->name);
+        print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER
+}
+
+static void init_sections(void)
+{
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
+        sections[i].show_all_entries = 1;
+}
+
+static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVDictionary *input_map = NULL;
+    AVDictionary *output_map = NULL;
+
+    print_int("graph_index", fg->index);
+    print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+    print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
+    print_str("description", fgp->graph_desc);
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
+
+    for (int i = 0; i < fg->nb_inputs; i++) {
+        InputFilterPriv *ifilter = ifp_from_ifilter(fg->inputs[i]);
+        enum AVMediaType media_type = ifilter->type;
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
+
+        print_int("input_index", ifilter->index);
+
+        if (ifilter->linklabel)
+            print_str("link_label", (const char*)ifilter->linklabel);
+
+        if (ifilter->filter) {
+            print_id("filter_id", ifilter->filter->name);
+            print_str("filter_name", ifilter->filter->filter->name);
+        }
+
+        if (ifilter->linklabel && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
+        else if (ifilter->opts.name && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->opts.name, 0);
+
+        print_str("media_type", av_get_media_type_string(media_type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
+
+    for (int i = 0; i < fg->nb_outputs; i++) {
+        OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
+
+        print_int("output_index", ofilter->index);
+
+        print_str("name", ofilter->name);
+
+        if (fg->outputs[i]->linklabel)
+            print_str("link_label", (const char*)fg->outputs[i]->linklabel);
+
+        if (ofilter->filter) {
+            print_id("filter_id", ofilter->filter->name);
+            print_str("filter_name", ofilter->filter->filter->name);
+        }
+
+        if (ofilter->name && ofilter->filter)
+            av_dict_set(&output_map, ofilter->filter->name, ofilter->name, 0);
+
+
+        print_str("media_type", av_get_media_type_string(fg->outputs[i]->type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
+
+    if (graph) {
+        AVTextFormatSectionContext sec_ctx = { 0 };
+
+        sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTERS);
+
+        if (gpc->is_diagram) {
+            print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+            print_str("description", fgp->graph_desc);
+            print_str("id", sec_ctx.context_id);
+        }
+
+        av_freep(&sec_ctx.context_id);
+
+        for (unsigned i = 0; i < graph->nb_filters; i++) {
+            AVFilterContext *filter = graph->filters[i];
+
+            if (gpc->skip_buffer_filters) {
+                if (av_dict_get(input_map, filter->name, NULL, 0))
+                    continue;
+                if (av_dict_get(output_map, filter->name, NULL, 0))
+                    continue;
+            }
+
+            sec_ctx.context_id = filter->name;
+
+            print_filter(gpc, filter, input_map, output_map);
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
+    }
+
+    // Clean up dictionaries
+    av_dict_free(&input_map);
+    av_dict_free(&output_map);
+}
+
+static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    AVTextFormatContext       *tfc = gpc->tfc;
+    AVBPrint                   buf;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    sec_ctx.context_id = "Inputs";
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+    print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
+
+    for (int n = nb_ifiles - 1; n >= 0; n--) {
+        InputFile *ifi = ifiles[n];
+        AVFormatContext *fc = ifi->ctx;
+
+        sec_ctx.context_id = av_asprintf("Input_%d", n);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
+        av_freep(&sec_ctx.context_id);
+
+        print_fmt("index", "%d", ifi->index);
+
+        if (fc) {
+            print_str("demuxer_name", fc->iformat->name);
+            if (fc->url) {
+                char *extension = get_extension(fc->url);
+                if (extension) {
+                    print_str("file_extension", extension);
+                    av_freep(&extension);
+                }
+                print_str("url", fc->url);
+            }
+        }
+
+        sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
+
+        av_freep(&sec_ctx.context_id);
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+            const AVCodecDescriptor *codec_desc;
+
+            if (!ist || !ist->par)
+                continue;
+
+            codec_desc = avcodec_descriptor_get(ist->par->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
+
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_in_%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else if (ist->dec) {
+                char char_buf[256];
+                av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
+                av_bprintf(&buf, "%s", "Attachment");
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
+                av_bprintf(&buf, "%s", "Data");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ist->index);
+
+            if (ist->dec)
+                print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
+
+
+    print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
+
+    for (int n = 0; n < nb_ifiles; n++) {
+        InputFile *ifi = ifiles[n];
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+
+            if (!ist->decoder)
+                continue;
+
+            sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_DECODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("source_id", "r_in_%d_%d", n, i);
+            print_fmt("id", "in_%d_%d", n, i);
+
+            ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            print_fmt("name", "%s", ist->dec->name);
+
+            print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_DECODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            ////const AVCodecDescriptor *codec_desc;
+
+            if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
+                continue;
+
+            ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_ENCODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "out__%d_%d", n, i);
+            print_fmt("dest_id", "r_out__%d_%d", n, i);
+
+            print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
+
+    for (int n = nb_ofiles - 1; n >= 0; n--) {
+        OutputFile *of = ofiles[n];
+        Muxer *muxer = (Muxer *)of;
+
+        if (!muxer->fc)
+            continue;
+
+        sec_ctx.context_id = av_asprintf("Output_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
+
+        av_freep(&sec_ctx.context_id);
+
+        ////print_str_opt("index", av_get_media_type_string(of->index));
+        print_fmt("index", "%d", of->index);
+        ////print_str("url", of->url);
+        print_str("muxer_name", muxer->fc->oformat->name);
+        if (of->url) {
+            char *extension = get_extension(of->url);
+            if (extension) {
+                print_str("file_extension", extension);
+                av_freep(&extension);
+            }
+            print_str("url", of->url);
+        }
+
+        sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_out__%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else {
+                av_bprintf(&buf, "%s", "unknown");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ost->index);
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
+
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->ist && !ost->filter) {
+                sec_ctx.context_type = av_get_media_type_string(ost->type);
+                avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_STREAMLINK);
+                sec_ctx.context_type = NULL;
+
+                if (ost->enc) {
+                    print_fmt("dest_stream_id", "out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Transcode");
+                } else {
+                    print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Stream Copy");
+                }
+
+                print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+                avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
+
+    return 0;
+}
+
+
+static void uninit_graphprint(GraphPrintContext *gpc)
+{
+    if (gpc->tfc)
+        avtext_context_close(&gpc->tfc);
+
+    if (gpc->wctx)
+        avtextwriter_context_close(&gpc->wctx);
+
+    // Finalize the print buffer if it was initialized
+    av_bprint_finalize(&gpc->pbuf, NULL);
+}
+
+static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
+{
+    const AVTextFormatter *text_formatter;
+    AVTextFormatContext *tfc = NULL;
+    AVTextWriterContext *wctx = NULL;
+    GraphPrintContext *gpc = NULL;
+    char *w_args = NULL;
+    char *w_name;
+    int ret;
+
+    init_sections();
+    *pgpc = NULL;
+
+    av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("json");
+    if (!print_graphs_format) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &w_args);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    text_formatter = avtext_get_formatter_by_name(w_name);
+    if (!text_formatter) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtextwriter_create_buffer(&wctx, target_buf);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), 0, 0, 0, 0, -1, NULL);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    gpc = av_mallocz(sizeof(GraphPrintContext));
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    gpc->wctx = wctx;
+    gpc->tfc = tfc;
+    av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
+    gpc->is_diagram = !!(tfc->formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
+    if (gpc->is_diagram) {
+        tfc->show_value_unit = 1;
+        tfc->show_optional_fields = -1;
+        gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+        gpc->skip_buffer_filters = 1;
+        ////} else {
+        ////    gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+    }
+
+    if (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml")) {
+        gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
+
+        if (!strcmp(text_formatter->name, "mermaidhtml"))
+            gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
+
+        av_diagram_init(tfc, &gpc->diagram_config);
+    }
+
+    *pgpc = gpc;
+
+    return 0;
+
+fail:
+    if (tfc)
+        avtext_context_close(&tfc);
+    if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
+        avtextwriter_context_close(&wctx);
+    av_freep(&gpc);
+
+    return ret;
+}
+
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVBPrint *target_buf = &fgp->graph_print_buf;
+    int ret;
+
+    if (!fg || !fgp) {
+        av_log(NULL, AV_LOG_ERROR, "Invalid filter graph provided\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (target_buf->len)
+        av_bprint_finalize(target_buf, NULL);
+
+    ret = init_graphprint(&gpc, target_buf);
+    if (ret)
+        return ret;
+
+    if (!gpc) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to initialize graph print context\n");
+        return AVERROR(ENOMEM);
+    }
+
+    tfc = gpc->tfc;
+
+    // Due to the threading model each graph needs to print itself into a buffer
+    // from its own thread. The actual printing happens short before cleanup in ffmpeg.c
+    // where all graphs are assembled together. To make this work, we need to put the
+    // formatting context into the same state like it would be when printing all at once,
+    // so here we print the section headers and clear the buffer to get into the right state.
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+
+    av_bprint_clear(target_buf);
+
+    print_filtergraph_single(gpc, fg, graph);
+
+    if (gpc->is_diagram) {
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+    }
+
+    uninit_graphprint(gpc);
+
+    return 0;
+}
+
+static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    AVBPrint target_buf;
+    int ret;
+
+    ret = init_graphprint(&gpc, &target_buf);
+    if (ret)
+        goto cleanup;
+
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto cleanup;
+    }
+
+    tfc = gpc->tfc;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+
+    for (int i = 0; i < nb_graphs; i++) {
+        FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
+        AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+        if (graph_buf->len > 0) {
+            avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+            av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+            av_bprint_finalize(graph_buf, NULL);
+            avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        }
+    }
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->fg_simple) {
+                FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
+                AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+                if (graph_buf->len > 0) {
+                    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+                    av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+                    av_bprint_finalize(graph_buf, NULL);
+                    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+                }
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+
+    print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ROOT
+
+    if (print_graphs_file) {
+        AVIOContext *avio = NULL;
+
+        if (!strcmp(print_graphs_file, "-")) {
+            printf("%s", target_buf.str);
+        } else {
+            ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
+                goto cleanup;
+            }
+
+            avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
+            avio_flush(avio);
+
+            if ((ret = avio_closep(&avio)) < 0)
+                av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
+        }
+    }
+
+    if (print_graphs)
+        av_log(NULL, AV_LOG_INFO, "%s    %c", target_buf.str, '\n');
+
+cleanup:
+    // Properly clean up resources
+    if (gpc)
+        uninit_graphprint(gpc);
+
+    // Ensure the target buffer is properly finalized
+    av_bprint_finalize(&target_buf, NULL);
+
+    return ret;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+}
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
new file mode 100644
index 0000000000..9f043cc273
--- /dev/null
+++ b/fftools/graph/graphprint.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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_GRAPH_GRAPHPRINT_H
+#define FFTOOLS_GRAPH_GRAPHPRINT_H
+
+#include "fftools/ffmpeg.h"
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles);
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index d96ced9b7d..f40a667843 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -710,6 +710,8 @@ static void formatters_register_all(void)
     registered_formatters[4] = &avtextformatter_ini;
     registered_formatters[5] = &avtextformatter_json;
     registered_formatters[6] = &avtextformatter_xml;
+    registered_formatters[7] = &avtextformatter_mermaid;
+    registered_formatters[8] = &avtextformatter_mermaidhtml;
 }
 
 const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index 16cd9b214f..4890cb846d 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -31,6 +31,12 @@
 
 #define SECTION_MAX_NB_CHILDREN 11
 
+typedef struct AVTextFormatSectionContext {
+    char *context_id;
+    const char *context_type;
+    int context_flags;
+} AVTextFormatSectionContext;
+
 
 typedef struct AVTextFormatSection {
     int id;             ///< unique id identifying a section
@@ -42,6 +48,10 @@ typedef struct AVTextFormatSection {
                                            ///  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
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE       32 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS      64 ///< ...
+#define AV_TEXTFORMAT_SECTION_PRINT_TAGS         128 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH   256 ///< ...
 
     int flags;
     const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
@@ -50,12 +60,17 @@ typedef struct AVTextFormatSection {
     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;
+    const char *id_key;          ///< name of the key to be used as the id 
+    const char *src_id_key;     ///< name of the key to be used as the source id for diagram connections
+    const char *dest_id_key;   ///< name of the key to be used as the target id for diagram connections
+    const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
 } AVTextFormatSection;
 
 typedef struct AVTextFormatContext AVTextFormatContext;
 
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
+#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER         4
 
 typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
@@ -64,6 +79,18 @@ typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_NB
 } StringValidation;
 
+typedef enum {
+    AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_NONDIR,
+    AV_TEXTFORMAT_LINKTYPE_HIDDEN,
+    AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
+} AVTextFormatLinkType;
+
 typedef struct AVTextFormatter {
     const AVClass *priv_class;      ///< private class of the formatter, if any
     int priv_size;                  ///< private size for the formatter context
@@ -167,5 +194,7 @@ extern const AVTextFormatter avtextformatter_flat;
 extern const AVTextFormatter avtextformatter_ini;
 extern const AVTextFormatter avtextformatter_json;
 extern const AVTextFormatter avtextformatter_xml;
+extern const AVTextFormatter avtextformatter_mermaid;
+extern const AVTextFormatter avtextformatter_mermaidhtml;
 
 #endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
diff --git a/fftools/textformat/tf_mermaid.c b/fftools/textformat/tf_mermaid.c
new file mode 100644
index 0000000000..e0ea89b02a
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.c
@@ -0,0 +1,655 @@
+/*
+ * 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 "tf_internal.h"
+#include "tf_mermaid.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+
+static const char *init_directive = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 10,"
+        "\"nodeSpacing\": 10,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"flowchart\": { "
+            "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
+            "\"diagramPadding\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char* init_directive_er = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"layout\": \"elk\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 65,"
+        "\"nodeSpacing\": 60,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"er\": { "
+            "\"diagramPadding\": 12, "
+            "\"entityPadding\": 4, "
+            "\"minEntityWidth\": 150, "
+            "\"minEntityHeight\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char *theme_css_er = ""
+
+    // Variables
+            ".root { "
+                "--ff-colvideo: #6eaa7b; "
+                "--ff-colaudio: #477fb3; "
+                "--ff-colsubtitle: #ad76ab; "
+                "--ff-coltext: #666; "
+            "} "
+            " g.nodes g.node.default rect.basic.label-container, "
+            " g.nodes g.node.default path { "
+            "     rx: 1; "
+            "     ry: 1; "
+            "     stroke-width: 1px !important; "
+            "     stroke: #e9e9e9 !important; "
+            "     fill: url(#ff-filtergradient) !important; "
+            "     filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
+            "     fill: white !important; "
+            " } "
+            "  "
+            " .relationshipLine { "
+            "     stroke: gray; "
+            "     stroke-width: 1; "
+            "     fill: none; "
+            "     filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
+            " } "
+            "  "
+            " g.node.default g.label.name  foreignObject > div > span > p, "
+            " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
+            "     font-size: 0.95rem; "
+            "     font-weight: 500; "
+            "     text-transform: uppercase; "
+            "     min-width: 5.5rem; "
+            "     margin-bottom: 0.5rem; "
+            "      "
+            " } "
+            "  "
+            " .edgePaths path { "
+            "     marker-end: none; "
+            "     marker-start: none; "
+            "  "
+            "} ";
+
+
+/* Mermaid Graph output */
+
+typedef struct MermaidContext {
+    const AVClass *class;
+    AVDiagramConfig *diagram_config;
+    int subgraph_count;
+    int within_tag;
+    int indent_level;
+    int create_html;
+
+    // Options
+    int enable_link_colors; // Requires Mermaid 11.5
+
+    struct section_data {
+        const char *section_id;
+        const char *section_type;
+        const char *src_id;
+        const char *dest_id;
+        AVTextFormatLinkType link_type;
+        int current_is_textblock;
+        int current_is_stadium;
+        int subgraph_start_incomplete;
+    }  section_data[SECTION_MAX_NB_LEVELS];
+
+    unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint link_buf; ///< print buffer for writing diagram links
+    AVDictionary *link_dict;
+} MermaidContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(MermaidContext, x)
+
+static const AVOption mermaid_options[] = {
+    { "link_coloring",    "enable colored links (requires Mermaid >= 11.5)",  OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL,   { .i64 = 1 },  0, 1 },
+    ////{"diagram_css",      "CSS for the diagram",                              OFFSET(diagram_css),        AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    ////{"html_template",    "Template HTML",                                    OFFSET(html_template),      AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    { NULL },
+};
+
+DEFINE_FORMATTER_CLASS(mermaid);
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
+{
+    MermaidContext *mmc = tfc->priv;
+    mmc->diagram_config = diagram_config;
+}
+
+static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
+{
+    MermaidContext *mmc = tfc->priv;
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&buf, "%s--%s", src, dest);
+
+    if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
+        return 1;
+
+    av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
+
+    return 0;
+}
+
+static av_cold int mermaid_init(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
+    return 0;
+}
+
+static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    int ret = mermaid_init(tfc);
+
+    if (ret < 0)
+        return ret;
+
+    mmc->create_html = 1;
+
+    return 0;
+}
+
+#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
+
+static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
+{
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSectionContext *sec_ctx = data;
+
+    if (tfc->level == 0) {
+        char *directive;
+        AVBPrint css_buf;
+        const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
+        char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
+        ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
+        av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+        av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
+        av_freep(&single_line_css);
+
+        directive = av_strireplace(diag_directive, "__###__", css_buf.str);
+        if (mmc->create_html) {
+            uint64_t length;
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+
+            length = token_pos - mmc->diagram_config->html_template;
+            for (uint64_t i = 0; i < length; i++)
+                writer_w8(tfc, mmc->diagram_config->html_template[i]);
+        }
+
+        writer_put_str(tfc, directive);
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+            writer_put_str(tfc, "flowchart LR\n");
+        ////writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
+            writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%);     \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%);   \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%);     \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            writer_put_str(tfc, "erDiagram\n");
+            break;
+        }
+
+        return;
+    }
+
+    if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
+        AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
+
+        if (parent_sec_data.subgraph_start_incomplete) {
+
+            if (parent_buf->len > 0)
+                writer_printf(tfc, "%s", parent_buf->str);
+
+            writer_put_str(tfc, "</div>\"]\n");
+
+            mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
+        }
+    }
+
+    av_freep(&mmc->section_data[tfc->level].section_id);
+    av_freep(&mmc->section_data[tfc->level].section_type);
+    av_freep(&mmc->section_data[tfc->level].src_id);
+    av_freep(&mmc->section_data[tfc->level].dest_id);
+    mmc->section_data[tfc->level].current_is_textblock = 0;
+    mmc->section_data[tfc->level].current_is_stadium = 0;
+    mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
+    mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
+
+    // NOTE: av_strdup() allocations aren't checked
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+            MM_INDENT();
+            writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+
+            mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+                if (sec_ctx->context_flags & 1) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_textblock = 1;
+                } else if (sec_ctx->context_flags & 2) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s([\"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_stadium = 1;
+                } else {
+                    MM_INDENT();
+                    writer_printf(tfc, "%s(\"", sec_ctx->context_id);
+                }
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+                MM_INDENT();
+                writer_printf(tfc, "%s {\n", sec_ctx->context_id);
+                break;
+            }
+
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
+
+        if (sec_ctx && sec_ctx->context_type)
+            writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
+        else
+            writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        av_bprint_clear(buf);
+        mmc->nb_link_captions[tfc->level] = 0;
+
+        if (sec_ctx && sec_ctx->context_type)
+            mmc->section_data[tfc->level].section_type = av_strdup(sec_ctx->context_type);
+
+        ////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(tfc, " type=\"%s\"", buf.str);
+    }
+}
+
+static void mermaid_print_section_footer(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
+        writer_put_str(tfc, "</div>");
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (sec_data.current_is_textblock) {
+                writer_printf(tfc, "\"}\n", section->name);
+
+                if (sec_data.section_id) {
+                    MM_INDENT();
+                    writer_put_str(tfc, "class ");
+                    writer_put_str(tfc, sec_data.section_id);
+                    writer_put_str(tfc, " ff-");
+                    writer_put_str(tfc, section->name);
+                    writer_put_str(tfc, "\n");
+                }
+            } else if (sec_data.current_is_stadium) {
+                writer_printf(tfc, "\"]):::ff-%s\n", section->name);
+            } else {
+                writer_printf(tfc, "\"):::ff-%s\n", section->name);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            MM_INDENT();
+            writer_put_str(tfc, "}\n\n");
+            break;
+        }
+
+        mmc->indent_level--;
+
+    } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
+
+        MM_INDENT();
+        writer_put_str(tfc, "end\n");
+
+        if (sec_data.section_id) {
+            MM_INDENT();
+            writer_put_str(tfc, "class ");
+            writer_put_str(tfc, sec_data.section_id);
+            writer_put_str(tfc, " ff-");
+            writer_put_str(tfc, section->name);
+            writer_put_str(tfc, "\n");
+        }
+
+        mmc->indent_level--;
+    }
+
+    if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
+        if (sec_data.src_id && sec_data.dest_id
+            && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+
+                if (sec_data.section_type && mmc->enable_link_colors)
+                    av_bprintf(&mmc->link_buf, "\n  %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
+                else
+                    av_bprintf(&mmc->link_buf, "\n  %s ==", sec_data.src_id);
+
+                if (buf->len > 0) {
+                    av_bprintf(&mmc->link_buf, " \"%s", buf->str);
+
+                    for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
+                        av_bprintf(&mmc->link_buf, "<br>&nbsp;");
+
+                    av_bprintf(&mmc->link_buf, "\" ==");
+                }
+
+                av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+
+                av_bprintf(&mmc->link_buf, "\n  %s", sec_data.src_id);
+
+                switch (sec_data.link_type) {
+                case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
+                    break;
+                default:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                }
+
+                av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
+
+                break;
+            }
+
+    if (tfc->level == 0) {
+
+        writer_put_str(tfc, "\n");
+        if (mmc->create_html) {
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+            token_pos += strlen("__###__");
+            writer_put_str(tfc, token_pos);
+        }
+    }
+
+    if (tfc->level == 1) {
+
+        if (mmc->link_buf.len > 0) {
+            writer_put_str(tfc, mmc->link_buf.str);
+            av_bprint_clear(&mmc->link_buf);
+        }
+
+        writer_put_str(tfc, "\n");
+    }
+}
+
+static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
+                                const char *str, int64_t num, const int is_int)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+    int exit = 0;
+
+    if (section->id_key && !strcmp(section->id_key, key)) {
+        mmc->section_data[tfc->level].section_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
+        mmc->section_data[tfc->level].dest_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->src_id_key && !strcmp(section->src_id_key, key)) {
+        mmc->section_data[tfc->level].src_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->linktype_key && !strcmp(section->linktype_key, key)) {
+        mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
+        exit = 1;
+    }
+
+    //if (exit)
+    //    return;
+
+    if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
+        || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
+
+        if (exit)
+            return;
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (is_int) {
+                writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
+            } else {
+                ////AVBPrint b;
+                ////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
+                const char *tmp = av_strireplace(str, "\"", "'");
+                ////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
+                writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
+                av_freep(&tmp);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            {
+                char *col_type;
+
+                if (key[0] == '_')
+                    return;
+
+                if (sec_data.section_id && !strcmp(str, sec_data.section_id))
+                    col_type = "PK";
+                else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
+                    col_type = "FK";
+                else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
+                    col_type = "FK";
+                else
+                    col_type = "";
+
+                MM_INDENT();
+
+                if (is_int)
+                    writer_printf(tfc, "    %s %"PRId64" %s\n", key, num, col_type);
+                else
+                    writer_printf(tfc, "    %s %s %s\n", key, str, col_type);
+            }
+            break;
+        }
+
+    } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        if (exit)
+            return;
+
+        if (buf->len > 0)
+            av_bprintf(buf, "%s", "<br>");
+
+        av_bprintf(buf, "");
+        if (is_int)
+            av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
+        else
+            av_bprintf(buf, "<span>%s</span>", str);
+
+        mmc->nb_link_captions[tfc->level]++;
+    }
+}
+
+static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
+{
+    mermaid_print_value(tfc, key, value, 0, 0);
+}
+
+static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
+{
+    mermaid_print_value(tfc, key, NULL, value, 1);
+}
+
+const AVTextFormatter avtextformatter_mermaid = {
+    .name                 = "mermaid",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
+
+
+const AVTextFormatter avtextformatter_mermaidhtml = {
+    .name                 = "mermaidhtml",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init_html,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
diff --git a/fftools/textformat/tf_mermaid.h b/fftools/textformat/tf_mermaid.h
new file mode 100644
index 0000000000..aff73bf9f3
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.h
@@ -0,0 +1,41 @@
+/*
+ * 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_TF_MERMAID_H
+#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
+
+typedef enum {
+    AV_DIAGRAMTYPE_GRAPH,
+    AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
+} AVDiagramType;
+
+typedef struct AVDiagramConfig {
+    AVDiagramType diagram_type;
+    const char *diagram_css;
+    const char *html_template;
+} AVDiagramConfig;
+
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
+
+void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
+
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */
\ No newline at end of file
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v2 10/10] fftools/graphprint: Now, make it a Killer-Feature!
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
                     ` (8 preceding siblings ...)
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 09/10] fftools/graphprint: Add execution graph printing softworkz
@ 2025-04-16 10:12   ` softworkz
  2025-04-16 10:21   ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing softworkz .
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-16 10:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

remember this: -sg   <= show-graph

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi              |   4 +
 fftools/Makefile             |   1 +
 fftools/ffmpeg.c             |   2 +-
 fftools/ffmpeg.h             |   1 +
 fftools/ffmpeg_filter.c      |   2 +-
 fftools/ffmpeg_opt.c         |   4 +
 fftools/graph/filelauncher.c | 204 +++++++++++++++++++++++++++++++++++
 fftools/graph/graphprint.c   |  50 ++++++++-
 fftools/graph/graphprint.h   |  32 ++++++
 9 files changed, 295 insertions(+), 5 deletions(-)
 create mode 100644 fftools/graph/filelauncher.c

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 35675b5309..6e9e7aed0e 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1404,6 +1404,10 @@ Writes execution graph details to the specified file in the format set via -prin
 Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
 The default format is json.
 
+@item -sg (@emph{global})
+Writes the execution graph to a temporary html file (mermaidhtml format) and 
+tries to launch it in the default browser.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index 8d87ea8255..1d1d8a818d 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -22,6 +22,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
     fftools/graph/graphprint.o        \
+    fftools/graph/filelauncher.o      \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
     fftools/textformat/avtextformat.o \
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index 6766ec209c..9875a1f7fd 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -309,7 +309,7 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
 
     if (do_benchmark) {
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7fbf0ad532..49fea0307d 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -721,6 +721,7 @@ extern int print_graphs;
 extern char *print_graphs_file;
 extern char *print_graphs_format;
 extern int auto_conversion_filters;
+extern int show_graph;
 
 extern const AVIOInterruptCB int_cb;
 
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index b774606562..e82e333b7f 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -2985,7 +2985,7 @@ read_frames:
 
 finish:
 
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraph(fg, fgt.graph);
 
     // EOF is normal termination
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3d1efe32f9..24713d640f 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -79,6 +79,7 @@ int vstats_version = 2;
 int print_graphs = 0;
 char *print_graphs_file = NULL;
 char *print_graphs_format = NULL;
+int show_graph = 0;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1748,6 +1749,9 @@ const OptionDef options[] = {
     { "print_graphs_format", OPT_TYPE_STRING, 0,
         { &print_graphs_format },
       "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
+    { "sg",   OPT_TYPE_BOOL, 0,
+        { &show_graph },
+        "create execution graph as temporary html file and try to launch it in the default browser" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/filelauncher.c b/fftools/graph/filelauncher.c
new file mode 100644
index 0000000000..ae9d88c2e4
--- /dev/null
+++ b/fftools/graph/filelauncher.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(_WIN32)
+#  include <windows.h>
+#else
+#  include <sys/time.h>
+#  include <time.h>
+#endif
+#include "graphprint.h"
+
+int ff_open_html_in_browser(const char *html_path)
+{
+    if (!html_path || !*html_path)
+        return -1;
+
+#if defined(_WIN32)
+
+    // --- Windows ---------------------------------
+    {
+        HINSTANCE rc = ShellExecuteA(NULL, "open", html_path, NULL, NULL, SW_SHOWNORMAL);
+        if ((UINT_PTR)rc <= 32) {
+            // Fallback: system("start ...")
+            char cmd[1024];
+            _snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "start \"\" \"%s\"", html_path);
+            if (system(cmd) != 0)
+                return -1;
+        }
+        return 0;
+    }
+
+#elif defined(__APPLE__)
+
+    // --- macOS -----------------------------------
+    {
+        // "open" is the macOS command to open a file/URL with the default application
+        char cmd[1024];
+        snprintf(cmd, sizeof(cmd), "open '%s' 1>/dev/null 2>&1 &", html_path);
+        if (system(cmd) != 0)
+            return -1;
+        return 0;
+    }
+
+#else
+
+    // --- Linux / Unix-like -----------------------
+    // We'll try xdg-open, then gnome-open, then kfmclient
+    {
+        // Helper macro to try one browser command
+        // Returns 0 on success, -1 on failure
+        #define TRY_CMD(prog) do {                                   \
+            char buf[1024];                                          \
+            snprintf(buf, sizeof(buf), "%s '%s' 1>/dev/null 2>&1 &", \
+                     (prog), html_path);                              \
+            int ret = system(buf);                                    \
+            /* On Unix: system() returns -1 if the shell can't run. */\
+            /* Otherwise, check exit code in lower 8 bits.           */\
+            if (ret != -1 && WIFEXITED(ret) && WEXITSTATUS(ret) == 0) \
+                return 0;                                             \
+        } while (0)
+
+        TRY_CMD("xdg-open");
+        TRY_CMD("gnome-open");
+        TRY_CMD("kfmclient exec");
+
+        fprintf(stderr, "Could not open '%s' in a browser.\n", html_path);
+        return -1;
+    }
+
+#endif
+}
+
+
+int ff_get_temp_dir(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    // --- Windows ------------------------------------
+    {
+        // GetTempPathA returns length of the string (including trailing backslash).
+        // If the return value is greater than buffer size, it's an error.
+        DWORD len = GetTempPathA((DWORD)size, buf);
+        if (len == 0 || len > size) {
+            // Could not retrieve or buffer is too small
+            return -1;
+        }
+        return 0;
+    }
+
+#else
+
+    // --- macOS / Linux / Unix -----------------------
+    // Follow typical POSIX convention: check common env variables
+    // and fallback to /tmp if not found.
+    {
+        const char *tmp = getenv("TMPDIR");
+        if (!tmp || !*tmp) tmp = getenv("TMP");
+        if (!tmp || !*tmp) tmp = getenv("TEMP");
+        if (!tmp || !*tmp) tmp = "/tmp";
+
+        // Copy into buf, ensure there's a trailing slash
+        size_t len = strlen(tmp);
+        if (len + 2 > size) {
+            // Need up to len + 1 for slash + 1 for null terminator
+            return -1;
+        }
+
+        strcpy(buf, tmp);
+        // Append slash if necessary
+        if (buf[len - 1] != '/' && buf[len - 1] != '\\') {
+#if defined(__APPLE__)
+            // On macOS/Unix, use forward slash
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#else
+            // Technically on Unix it's always '/', but here's how you'd do if needed:
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#endif
+        }
+        return 0;
+    }
+
+#endif
+}
+
+int ff_make_timestamped_html_name(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    /*----------- Windows version -----------*/
+    SYSTEMTIME st;
+    GetLocalTime(&st);
+    /*
+      st.wYear, st.wMonth, st.wDay,
+      st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
+    */
+    int written = _snprintf_s(buf, size, _TRUNCATE,
+                              "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                              st.wYear,
+                              st.wMonth,
+                              st.wDay,
+                              st.wHour,
+                              st.wMinute,
+                              st.wSecond,
+                              st.wMilliseconds);
+    if (written < 0)
+        return -1; /* Could not write into buffer */
+    return 0;
+
+#else
+
+    /*----------- macOS / Linux / Unix version -----------*/
+    struct timeval tv;
+    if (gettimeofday(&tv, NULL) != 0) {
+        return -1; /* gettimeofday failed */
+    }
+
+    struct tm local_tm;
+    localtime_r(&tv.tv_sec, &local_tm);
+
+    int ms = (int)(tv.tv_usec / 1000); /* convert microseconds to milliseconds */
+
+    /* 
+       local_tm.tm_year is years since 1900,
+       local_tm.tm_mon  is 0-based (0=Jan, 11=Dec) 
+    */
+    int written = snprintf(buf, size,
+                           "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                           local_tm.tm_year + 1900,
+                           local_tm.tm_mon + 1,
+                           local_tm.tm_mday,
+                           local_tm.tm_hour,
+                           local_tm.tm_min,
+                           local_tm.tm_sec,
+                           ms);
+    if (written < 0 || (size_t)written >= size) {
+        return -1; /* Buffer too small or formatting error */
+    }
+    return 0;
+
+#endif
+}
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
index 89c38d2e36..8241c51e6c 100644
--- a/fftools/graph/graphprint.c
+++ b/fftools/graph/graphprint.c
@@ -586,8 +586,6 @@ static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifil
     AVBPrint                   buf;
     AVTextFormatSectionContext sec_ctx = { 0 };
 
-    sec_ctx.context_id = "Inputs";
-
     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
 
     print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
@@ -875,6 +873,11 @@ static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
 
     av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
 
+    if (show_graph) {
+        if (!print_graphs_format || strcmp(print_graphs_format, "mermaidhtml") != 0)
+            print_graphs_format = av_strdup("mermaidhtml");
+    }
+
     if (!print_graphs_format)
         print_graphs_format = av_strdup("json");
     if (!print_graphs_format) {
@@ -1098,5 +1101,46 @@ cleanup:
 
 int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
 {
-    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+    int ret;
+
+    if (show_graph) {
+        char buf[2048];
+        AVBPrint bp;
+
+        av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+        print_graphs = 0;
+
+        ret = ff_get_temp_dir(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error getting temp directory path for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        ret = ff_make_timestamped_html_name(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error creating temp file name for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        av_bprint_finalize(&bp, &print_graphs_file);
+    }
+
+    ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    if (!ret && show_graph) {
+        av_log(NULL, AV_LOG_INFO, "Execution graph saved as: %s\n", print_graphs_file);
+        av_log(NULL, AV_LOG_INFO, "Trying to launch graph in browser...\n");
+
+        ret = ff_open_html_in_browser(print_graphs_file);
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Browser could not be launched for execution graph display\nPlease open manually: %s\n", print_graphs_file);
+        }
+    }
+
+    return ret;
 }
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
index 9f043cc273..43f769870b 100644
--- a/fftools/graph/graphprint.h
+++ b/fftools/graph/graphprint.h
@@ -27,4 +27,36 @@ int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles,
 
 int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
 
+/**
+ * Open an HTML file in the default browser (Windows, macOS, Linux/Unix).
+ *
+ * @param html_path Absolute or relative path to the HTML file.
+ * @return 0 on success, -1 on failure.
+ *
+ * NOTE: This uses system() calls for non-Windows, and ShellExecute on Windows.
+ *       Exercise caution if 'html_path' is untrusted (possible command injection).
+ */
+int ff_open_html_in_browser(const char *html_path);
+
+/**
+ * Retrieve the system's temporary directory.
+ *
+ * @param buf  Output buffer to store the temp directory path (including trailing slash)
+ * @param size Size of the output buffer in bytes
+ * @return 0 on success, -1 on failure (buffer too small or other errors)
+ *
+ * Note: On most platforms, the path will include a trailing slash (e.g. "C:\\Users\\...\\Temp\\" on Windows, "/tmp/" on Unix).
+ */
+int ff_get_temp_dir(char *buf, size_t size);
+
+/**
+ * Create a timestamped HTML filename, e.g.:
+ *   ffmpeg_graph_2024-01-01_22-12-59_123.html
+ *
+ * @param buf  Pointer to buffer where the result is stored
+ * @param size Size of the buffer in bytes
+ * @return 0 on success, -1 on error (e.g. buffer too small)
+ */
+int ff_make_timestamped_html_name(char *buf, size_t size);
+
 #endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
                     ` (9 preceding siblings ...)
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 10/10] fftools/graphprint: Now, make it a Killer-Feature! softworkz
@ 2025-04-16 10:21   ` softworkz .
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-16 10:21 UTC (permalink / raw)
  To: ffmpegagent, ffmpeg-devel



> -----Original Message-----
> From: ffmpegagent <ffmpegagent@gmail.com>
> Sent: Mittwoch, 16. April 2025 12:12
> To: ffmpeg-devel@ffmpeg.org
> Cc: softworkz <softworkz@hotmail.com>
> Subject: [PATCH v2 00/10] Execution Graph Printing
> 
> Shortest cover letter for my longest-running FFmpeg patchset:
> 
>  * Apply
>  * Build
>  * Add the "-sg" switch to any FFmpeg command line
>  * Press 'q' when you don't want to wait
> 
> SG = Show Graph
> 
> Documentation and examples can be found here:
> 
> https://github.com/softworkz/ffmpeg_output_apis/wiki
> 
> 
> ===============

Hello,

has anybody tried this on a Mac? 

Or would someone be able to try? It's only about whether the
browser-launch is working when specifying -sg, as I don't
have a Mac available.

Thanks
sw




_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat: Quality improvements
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat: Quality improvements softworkz
@ 2025-04-16 10:49     ` Andreas Rheinhardt
  2025-04-16 11:33       ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Andreas Rheinhardt @ 2025-04-16 10:49 UTC (permalink / raw)
  To: ffmpeg-devel

softworkz:
> From: softworkz <softworkz@hotmail.com>
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/avtextformat.c | 111 +++++++++++++++++++-----------
>  fftools/textformat/avtextformat.h |   6 +-
>  fftools/textformat/tf_default.c   |   8 ++-
>  fftools/textformat/tf_ini.c       |   2 +-
>  fftools/textformat/tf_json.c      |   8 ++-
>  fftools/textformat/tf_xml.c       |   3 -
>  fftools/textformat/tw_avio.c      |   9 ++-
>  7 files changed, 93 insertions(+), 54 deletions(-)
> 
> diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
> index 811b14b999..4c8def8e65 100644
> --- a/fftools/textformat/avtextformat.c
> +++ b/fftools/textformat/avtextformat.c
> @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
>  
>  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++)
> +    for (unsigned i = 0; i < ubuf_size; i++)
>          av_bprintf(bp, "%02X", ubuf[i]);
>  }
>  
> @@ -141,7 +140,10 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
>      AVTextFormatContext *tctx;
>      int i, ret = 0;
>  
> -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> +    if (!ptctx || !formatter)
> +        return AVERROR(EINVAL);
> +
> +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
> @@ -213,25 +215,26 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
>                      av_log(NULL, AV_LOG_ERROR, " %s", n);
>                  av_log(NULL, AV_LOG_ERROR, "\n");
>              }
> -            return ret;
> +            goto fail;
>          }
>  
>      /* validate replace string */
>      {
> -        const uint8_t *p = tctx->string_validation_replacement;
> -        const uint8_t *endp = p + strlen(p);
> +        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
> +        const uint8_t *endp = p + strlen((const char *)p);

We use -Wno-pointer-sign in order to avoid these ugly casts.

>          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);
> +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>                  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;
> +                av_bprint_finalize(&bp, NULL);

You know that this is supposed to be for a single UTF-8 code point? It
is not supposed to write hundreds of bytes (although this may happen
with crazy input).

> +                goto fail;
>              }
>          }
>      }
> @@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
>  
>  void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
>  {
> +    if (section_id < 0 || section_id >= tctx->nb_sections)
> +        return;
> +
>      tctx->level++;
>      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
>  
> @@ -272,6 +278,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
>  
>  void avtext_print_section_footer(AVTextFormatContext *tctx)
>  {
> +    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return;
> +
>      int section_id = tctx->section[tctx->level]->id;
>      int parent_section_id = tctx->level
>          ? tctx->section[tctx->level - 1]->id
> @@ -289,7 +298,12 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
>  
>  void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
>  {
> -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
> +    const AVTextFormatSection *section;
> +
> +    if (!key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return;
> +
> +    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);
> @@ -299,24 +313,25 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
>  
>  static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
>  {
> -    const uint8_t *p, *endp;
> +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
>      AVBPrint dstbuf;
> +    AVBPrint bp;
>      int invalid_chars_nb = 0, ret = 0;
>  
> +    *dstp = NULL;
>      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
> +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>  
> -    endp = src + strlen(src);
> -    for (p = src; *p;) {
> -        uint32_t code;
> +    endp = srcp + strlen(src);
> +    for (p = srcp; *p;) {
> +        int32_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);
> +            av_bprint_clear(&bp);
> +            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;
>          }
>  
> @@ -336,7 +351,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
>          }
>  
>          if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
> -            av_bprint_append_data(&dstbuf, p0, p-p0);
> +            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
>      }
>  
>      if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
> @@ -346,6 +361,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
>  
>  end:
>      av_bprint_finalize(&dstbuf, dstp);
> +    av_bprint_finalize(&bp, NULL);
>      return ret;
>  }
>  
> @@ -358,17 +374,18 @@ struct unit_value {
>      const char *unit;
>  };
>  
> -static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
> +static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
>  {
>      double vald;
> -    int64_t vali;
> +    int64_t vali = 0;
>      int show_float = 0;
>  
>      if (uv.unit == unit_second_str) {
>          vald = uv.val.d;
>          show_float = 1;
>      } else {
> -        vald = vali = uv.val.i;
> +        vald = (double)uv.val.i;
> +        vali = uv.val.i;
>      }
>  
>      if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
> @@ -387,17 +404,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
>              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);
> +                index = (int64_t)(log2(vald) / 10);
> +                index = av_clip64(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);
> +                index = (int64_t)(log10(vald) / 3);
> +                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
>                  vald /= si_prefixes[index].dec_val;
>                  prefix_string = si_prefixes[index].dec_str;
>              }
> -            vali = vald;
> +            vali = (int64_t)vald;
>          }
>  
>          if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
> @@ -425,9 +442,14 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
>  
>  int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
>  {
> -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
> +    const AVTextFormatSection *section;
>      int ret = 0;
>  
> +    if (!key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return AVERROR(EINVAL);
> +
> +    section = tctx->section[tctx->level];
> +
>      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
>          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
>              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
> @@ -469,12 +491,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
>  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);
> +        char buf[128];
> +        double d = av_q2d(*time_base) * ts;
>          struct unit_value uv;
>          uv.val.d = d;
>          uv.unit = unit_second_str;
> @@ -495,7 +516,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
>                         const uint8_t *data, int size)
>  {
>      AVBPrint bp;
> -    int offset = 0, l, i;
> +    unsigned offset = 0;
> +    int l, i;
>  
>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>      av_bprintf(&bp, "\n");
> @@ -522,25 +544,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
>  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 };
> +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
> +    int len;
>  
>      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);
> +    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
> +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
>      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)
> +                           uint8_t *data, int size, const char *format,
> +                           int columns, int bytes, int offset_add)
>  {
>      AVBPrint bp;
> -    int offset = 0, l, i;
> +    unsigned offset = 0;
> +    int l, i;
> +
> +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
> +        return;
>  
>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>      av_bprintf(&bp, "\n");
> @@ -607,12 +633,15 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
>      AVTextWriterContext *wctx;
>      int ret = 0;
>  
> -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
> +    if (!pwctx || !writer)
> +        return AVERROR(EINVAL);
> +
> +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
>  
> -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
> +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
> diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
> index c598af3450..aea691f351 100644
> --- a/fftools/textformat/avtextformat.h
> +++ b/fftools/textformat/avtextformat.h
> @@ -21,9 +21,7 @@
>  #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"
> @@ -103,7 +101,7 @@ struct AVTextFormatContext {
>      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];
> +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
>      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
>                                                    ///  used by various formatters
>  
> @@ -124,7 +122,7 @@ struct AVTextFormatContext {
>  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
>  
>  int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
> -                        const struct AVTextFormatSection *sections, int nb_sections,
> +                        const AVTextFormatSection *sections, int nb_sections,
>                          int show_value_unit,
>                          int use_value_prefix,
>                          int use_byte_value_binary_prefix,
> diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
> index 2c5047eafd..ad97173b0b 100644
> --- a/fftools/textformat/tf_default.c
> +++ b/fftools/textformat/tf_default.c
> @@ -68,9 +68,10 @@ 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;
> +    unsigned i;
> +
>      for (i = 0; src[i] && i < dst_size - 1; i++)
> -        dst[i] = av_toupper(src[i]);
> +        dst[i] = (char)av_toupper(src[i]);
>      dst[i] = 0;
>      return dst;
>  }
> @@ -106,6 +107,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
>      const struct AVTextFormatSection *section = wctx->section[wctx->level];
>      char buf[32];
>  
> +    if (!section)
> +        return;
> +
>      if (def->noprint_wrappers || def->nested_section[wctx->level])
>          return;
>  
> diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
> index 88add0819a..dd77d0e8bf 100644
> --- a/fftools/textformat/tf_ini.c
> +++ b/fftools/textformat/tf_ini.c
> @@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
>              /* fallthrough */
>          default:
>              if ((unsigned char)c < 32)
> -                av_bprintf(dst, "\\x00%02x", c & 0xff);
> +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
>              else
>                  av_bprint_chars(dst, c, 1);
>              break;
> diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
> index b61d3740c6..e86cdbf5d9 100644
> --- a/fftools/textformat/tf_json.c
> +++ b/fftools/textformat/tf_json.c
> @@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
>      static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
>      const char *p;
>  
> +    if (!src) {
> +        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source string\n");
> +        return NULL;
> +    }
> +
>      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);
> +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
>          } else {
>              av_bprint_chars(dst, *p, 1);
>          }
> @@ -105,6 +110,7 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
>          wctx->section[wctx->level-1] : NULL;
>  
>      if (wctx->level && wctx->nb_item[wctx->level-1])
> +    if (wctx->level && wctx->nb_item[wctx->level - 1])
>          writer_put_str(wctx, ",\n");
>  
>      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
> diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
> index befb39246d..28abfc6400 100644
> --- a/fftools/textformat/tf_xml.c
> +++ b/fftools/textformat/tf_xml.c
> @@ -18,10 +18,7 @@
>   * 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"
> diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
> index 6034f74ec9..b1743fb43a 100644
> --- a/fftools/textformat/tw_avio.c
> +++ b/fftools/textformat/tw_avio.c
> @@ -53,7 +53,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
>  static void io_put_str(AVTextWriterContext *wctx, const char *str)
>  {
>      IOWriterContext *ctx = wctx->priv;
> -    avio_write(ctx->avio_context, str, strlen(str));
> +    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
>  }
>  
>  static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> @@ -78,10 +78,12 @@ const AVTextWriter avtextwriter_avio = {
>  
>  int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
>  {
> +    if (!pwctx || !output_filename || !output_filename[0])
> +        return AVERROR(EINVAL);

You said in [1] that you removed all these impossible checks, yet here
they are; they are also above.

[1]: https://ffmpeg.org/pipermail/ffmpeg-devel/2025-April/342317.html

> +
>      IOWriterContext *ctx;
>      int ret;
>  
> -
>      ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
>      if (ret < 0)
>          return ret;
> @@ -103,6 +105,9 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
>  
>  int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
>  {
> +    if (!pwctx || !avio_ctx)
> +        return AVERROR(EINVAL);
> +
>      IOWriterContext *ctx;
>      int ret;
>  

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 04/10] fftools/tf_internal: Use ac_default_item_name
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 04/10] fftools/tf_internal: Use ac_default_item_name softworkz
@ 2025-04-16 10:50     ` Andreas Rheinhardt
  2025-04-16 11:11       ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Andreas Rheinhardt @ 2025-04-16 10:50 UTC (permalink / raw)
  To: ffmpeg-devel

softworkz:
> From: softworkz <softworkz@hotmail.com>
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/tf_internal.h | 6 +-----
>  1 file changed, 1 insertion(+), 5 deletions(-)
> 
> diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
> index 7b326328cb..e145bc83bb 100644
> --- a/fftools/textformat/tf_internal.h
> +++ b/fftools/textformat/tf_internal.h
> @@ -29,13 +29,9 @@
>  #include "avtextformat.h"
>  
>  #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,                  \
> +    .item_name  = av_default_item_name,             \
>      .option     = name##_options                    \
>  }
>  

I really don't know why you want to do this yourself instead of just
using my patch.

- Andreas

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 04/10] fftools/tf_internal: Use ac_default_item_name
  2025-04-16 10:50     ` Andreas Rheinhardt
@ 2025-04-16 11:11       ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-16 11:11 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Andreas Rheinhardt
> Sent: Mittwoch, 16. April 2025 12:51
> To: ffmpeg-devel@ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH v2 04/10] fftools/tf_internal: Use
> ac_default_item_name
> 
> softworkz:
> > From: softworkz <softworkz@hotmail.com>
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  fftools/textformat/tf_internal.h | 6 +-----
> >  1 file changed, 1 insertion(+), 5 deletions(-)
> >
> > diff --git a/fftools/textformat/tf_internal.h
> b/fftools/textformat/tf_internal.h
> > index 7b326328cb..e145bc83bb 100644
> > --- a/fftools/textformat/tf_internal.h
> > +++ b/fftools/textformat/tf_internal.h
> > @@ -29,13 +29,9 @@
> >  #include "avtextformat.h"
> >
> >  #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,                  \
> > +    .item_name  = av_default_item_name,             \
> >      .option     = name##_options                    \
> >  }
> >
> 
> I really don't know why you want to do this yourself instead of just
> using my patch.
> 
> - Andreas

I don't understand. Your patch is making changes to the 
DEFINE_FORMATTER_CLASS macro in every single tf_* file.
But these are already consolidate and removed from those
files, so your patch patched something that is removed in 
the new patchset.

What am I missing?

Thanks,
sw

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat: Quality improvements
  2025-04-16 10:49     ` Andreas Rheinhardt
@ 2025-04-16 11:33       ` softworkz .
  2025-04-18  2:48         ` softworkz .
  2025-04-18  5:41         ` softworkz .
  0 siblings, 2 replies; 130+ messages in thread
From: softworkz . @ 2025-04-16 11:33 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Andreas Rheinhardt
> Sent: Mittwoch, 16. April 2025 12:49
> To: ffmpeg-devel@ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat:
> Quality improvements
> 
> softworkz:
> > From: softworkz <softworkz@hotmail.com>
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  fftools/textformat/avtextformat.c | 111 +++++++++++++++++++--------
> ---
> >  fftools/textformat/avtextformat.h |   6 +-
> >  fftools/textformat/tf_default.c   |   8 ++-
> >  fftools/textformat/tf_ini.c       |   2 +-
> >  fftools/textformat/tf_json.c      |   8 ++-
> >  fftools/textformat/tf_xml.c       |   3 -
> >  fftools/textformat/tw_avio.c      |   9 ++-
> >  7 files changed, 93 insertions(+), 54 deletions(-)
> >
> > diff --git a/fftools/textformat/avtextformat.c
> b/fftools/textformat/avtextformat.c
> > index 811b14b999..4c8def8e65 100644
> > --- a/fftools/textformat/avtextformat.c
> > +++ b/fftools/textformat/avtextformat.c
> > @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
> >
> >  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++)
> > +    for (unsigned i = 0; i < ubuf_size; i++)
> >          av_bprintf(bp, "%02X", ubuf[i]);
> >  }
> >
> > @@ -141,7 +140,10 @@ int avtext_context_open(AVTextFormatContext
> **ptctx,
> >      AVTextFormatContext *tctx;
> >      int i, ret = 0;
> >
> > -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> > +    if (!ptctx || !formatter)
> > +        return AVERROR(EINVAL);
> > +
> > +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> > @@ -213,25 +215,26 @@ int avtext_context_open(AVTextFormatContext
> **ptctx,
> >                      av_log(NULL, AV_LOG_ERROR, " %s", n);
> >                  av_log(NULL, AV_LOG_ERROR, "\n");
> >              }
> > -            return ret;
> > +            goto fail;
> >          }
> >
> >      /* validate replace string */
> >      {
> > -        const uint8_t *p = tctx->string_validation_replacement;
> > -        const uint8_t *endp = p + strlen(p);
> > +        const uint8_t *p = (uint8_t *)tctx-
> >string_validation_replacement;
> > +        const uint8_t *endp = p + strlen((const char *)p);
> 
> We use -Wno-pointer-sign in order to avoid these ugly casts.

Yep, I know. Since I'm not a C-for-life developer, I'm using and 
taking attention of warnings and hints like clang-tidy.
When you have a file with dozens of warnings these things are not
helpful, because you cannot go through all of them a hundred times.
When disabling certain warnings altogether, nothing is won, because
often there's one in ten cases where the it hints at a problem while
all others are harmless. 
In order to get rid of a warning, you can either add ugly comments 
or - the designated way from the compiler side is to be explicit by
e.g. making explicit casts. That's why you see them at some places.
Doing so, improves quality when working - even for languages that 
I know in a similar way like you know C.

If we could agree to remove these in a future commit, it would be 
great. Otherwise, I can drop them right now as well.


> 
> >          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);
> > +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >                  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;
> > +                av_bprint_finalize(&bp, NULL);
> 
> You know that this is supposed to be for a single UTF-8 code point? It
> is not supposed to write hundreds of bytes (although this may happen
> with crazy input).
> 
> > +                goto fail;
> >              }
> >          }
> >      }
> > @@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] =
> "bit/s";
> >
> >  void avtext_print_section_header(AVTextFormatContext *tctx, const
> void *data, int section_id)
> >  {
> > +    if (section_id < 0 || section_id >= tctx->nb_sections)
> > +        return;
> > +
> >      tctx->level++;
> >      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
> >
> > @@ -272,6 +278,9 @@ void
> avtext_print_section_header(AVTextFormatContext *tctx, const void
> *data, in
> >
> >  void avtext_print_section_footer(AVTextFormatContext *tctx)
> >  {
> > +    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> > +        return;
> > +
> >      int section_id = tctx->section[tctx->level]->id;
> >      int parent_section_id = tctx->level
> >          ? tctx->section[tctx->level - 1]->id
> > @@ -289,7 +298,12 @@ void
> avtext_print_section_footer(AVTextFormatContext *tctx)
> >
> >  void avtext_print_integer(AVTextFormatContext *tctx, const char
> *key, int64_t val)
> >  {
> > -    const struct AVTextFormatSection *section = tctx->section[tctx-
> >level];
> > +    const AVTextFormatSection *section;
> > +
> > +    if (!key || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return;
> > +
> > +    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);
> > @@ -299,24 +313,25 @@ void avtext_print_integer(AVTextFormatContext
> *tctx, const char *key, int64_t va
> >
> >  static inline int validate_string(AVTextFormatContext *tctx, char
> **dstp, const char *src)
> >  {
> > -    const uint8_t *p, *endp;
> > +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
> >      AVBPrint dstbuf;
> > +    AVBPrint bp;
> >      int invalid_chars_nb = 0, ret = 0;
> >
> > +    *dstp = NULL;
> >      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
> > +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >
> > -    endp = src + strlen(src);
> > -    for (p = src; *p;) {
> > -        uint32_t code;
> > +    endp = srcp + strlen(src);
> > +    for (p = srcp; *p;) {
> > +        int32_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);
> > +            av_bprint_clear(&bp);
> > +            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;
> >          }
> >
> > @@ -336,7 +351,7 @@ static inline int
> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >          }
> >
> >          if (!invalid || tctx->string_validation ==
> AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
> > -            av_bprint_append_data(&dstbuf, p0, p-p0);
> > +            av_bprint_append_data(&dstbuf, (const char *)p0, p -
> p0);
> >      }
> >
> >      if (invalid_chars_nb && tctx->string_validation ==
> AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
> > @@ -346,6 +361,7 @@ static inline int
> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >
> >  end:
> >      av_bprint_finalize(&dstbuf, dstp);
> > +    av_bprint_finalize(&bp, NULL);
> >      return ret;
> >  }
> >
> > @@ -358,17 +374,18 @@ struct unit_value {
> >      const char *unit;
> >  };
> >
> > -static char *value_string(AVTextFormatContext *tctx, char *buf, int
> buf_size, struct unit_value uv)
> > +static char *value_string(const AVTextFormatContext *tctx, char
> *buf, int buf_size, struct unit_value uv)
> >  {
> >      double vald;
> > -    int64_t vali;
> > +    int64_t vali = 0;
> >      int show_float = 0;
> >
> >      if (uv.unit == unit_second_str) {
> >          vald = uv.val.d;
> >          show_float = 1;
> >      } else {
> > -        vald = vali = uv.val.i;
> > +        vald = (double)uv.val.i;
> > +        vali = uv.val.i;
> >      }
> >
> >      if (uv.unit == unit_second_str && tctx-
> >use_value_sexagesimal_format) {
> > @@ -387,17 +404,17 @@ static char *value_string(AVTextFormatContext
> *tctx, char *buf, int buf_size, st
> >              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);
> > +                index = (int64_t)(log2(vald) / 10);
> > +                index = av_clip64(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);
> > +                index = (int64_t)(log10(vald) / 3);
> > +                index = av_clip64(index, 0,
> FF_ARRAY_ELEMS(si_prefixes) - 1);
> >                  vald /= si_prefixes[index].dec_val;
> >                  prefix_string = si_prefixes[index].dec_str;
> >              }
> > -            vali = vald;
> > +            vali = (int64_t)vald;
> >          }
> >
> >          if (show_float || (tctx->use_value_prefix && vald !=
> (int64_t)vald))
> > @@ -425,9 +442,14 @@ void avtext_print_unit_int(AVTextFormatContext
> *tctx, const char *key, int value
> >
> >  int avtext_print_string(AVTextFormatContext *tctx, const char *key,
> const char *val, int flags)
> >  {
> > -    const struct AVTextFormatSection *section = tctx->section[tctx-
> >level];
> > +    const AVTextFormatSection *section;
> >      int ret = 0;
> >
> > +    if (!key || !val || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return AVERROR(EINVAL);
> > +
> > +    section = tctx->section[tctx->level];
> > +
> >      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
> >          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
> >              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
> > @@ -469,12 +491,11 @@ void avtext_print_rational(AVTextFormatContext
> *tctx, const char *key, AVRationa
> >  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);
> > +        char buf[128];
> > +        double d = av_q2d(*time_base) * ts;
> >          struct unit_value uv;
> >          uv.val.d = d;
> >          uv.unit = unit_second_str;
> > @@ -495,7 +516,8 @@ void avtext_print_data(AVTextFormatContext
> *tctx, const char *name,
> >                         const uint8_t *data, int size)
> >  {
> >      AVBPrint bp;
> > -    int offset = 0, l, i;
> > +    unsigned offset = 0;
> > +    int l, i;
> >
> >      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >      av_bprintf(&bp, "\n");
> > @@ -522,25 +544,29 @@ void avtext_print_data(AVTextFormatContext
> *tctx, const char *name,
> >  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 };
> > +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
> > +    int len;
> >
> >      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);
> > +    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx-
> >hash));
> > +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len],
> (int)sizeof(buf) - len);
> >      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)
> > +                           uint8_t *data, int size, const char
> *format,
> > +                           int columns, int bytes, int offset_add)
> >  {
> >      AVBPrint bp;
> > -    int offset = 0, l, i;
> > +    unsigned offset = 0;
> > +    int l, i;
> > +
> > +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
> > +        return;
> >
> >      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >      av_bprintf(&bp, "\n");
> > @@ -607,12 +633,15 @@ int
> avtextwriter_context_open(AVTextWriterContext **pwctx, const
> AVTextWriter *w
> >      AVTextWriterContext *wctx;
> >      int ret = 0;
> >
> > -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
> > +    if (!pwctx || !writer)
> > +        return AVERROR(EINVAL);
> > +
> > +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> >
> > -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
> > +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer-
> >priv_size)))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> > diff --git a/fftools/textformat/avtextformat.h
> b/fftools/textformat/avtextformat.h
> > index c598af3450..aea691f351 100644
> > --- a/fftools/textformat/avtextformat.h
> > +++ b/fftools/textformat/avtextformat.h
> > @@ -21,9 +21,7 @@
> >  #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"
> > @@ -103,7 +101,7 @@ struct AVTextFormatContext {
> >      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];
> > +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
> >      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic
> print buffer dedicated to each section,
> >                                                    ///  used by
> various formatters
> >
> > @@ -124,7 +122,7 @@ struct AVTextFormatContext {
> >  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
> >
> >  int avtext_context_open(AVTextFormatContext **ptctx, const
> AVTextFormatter *formatter, AVTextWriterContext *writer_context, const
> char *args,
> > -                        const struct AVTextFormatSection *sections,
> int nb_sections,
> > +                        const AVTextFormatSection *sections, int
> nb_sections,
> >                          int show_value_unit,
> >                          int use_value_prefix,
> >                          int use_byte_value_binary_prefix,
> > diff --git a/fftools/textformat/tf_default.c
> b/fftools/textformat/tf_default.c
> > index 2c5047eafd..ad97173b0b 100644
> > --- a/fftools/textformat/tf_default.c
> > +++ b/fftools/textformat/tf_default.c
> > @@ -68,9 +68,10 @@ 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;
> > +    unsigned i;
> > +
> >      for (i = 0; src[i] && i < dst_size - 1; i++)
> > -        dst[i] = av_toupper(src[i]);
> > +        dst[i] = (char)av_toupper(src[i]);
> >      dst[i] = 0;
> >      return dst;
> >  }
> > @@ -106,6 +107,9 @@ static void
> default_print_section_footer(AVTextFormatContext *wctx)
> >      const struct AVTextFormatSection *section = wctx->section[wctx-
> >level];
> >      char buf[32];
> >
> > +    if (!section)
> > +        return;
> > +
> >      if (def->noprint_wrappers || def->nested_section[wctx->level])
> >          return;
> >
> > diff --git a/fftools/textformat/tf_ini.c
> b/fftools/textformat/tf_ini.c
> > index 88add0819a..dd77d0e8bf 100644
> > --- a/fftools/textformat/tf_ini.c
> > +++ b/fftools/textformat/tf_ini.c
> > @@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const
> char *src)
> >              /* fallthrough */
> >          default:
> >              if ((unsigned char)c < 32)
> > -                av_bprintf(dst, "\\x00%02x", c & 0xff);
> > +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
> >              else
> >                  av_bprint_chars(dst, c, 1);
> >              break;
> > diff --git a/fftools/textformat/tf_json.c
> b/fftools/textformat/tf_json.c
> > index b61d3740c6..e86cdbf5d9 100644
> > --- a/fftools/textformat/tf_json.c
> > +++ b/fftools/textformat/tf_json.c
> > @@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint
> *dst, const char *src, void *log_ctx
> >      static const char json_subst[]  = { '"', '\\',  'b',  'f',
> 'n',  'r',  't', 0 };
> >      const char *p;
> >
> > +    if (!src) {
> > +        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source
> string\n");
> > +        return NULL;
> > +    }
> > +
> >      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);
> > +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
> >          } else {
> >              av_bprint_chars(dst, *p, 1);
> >          }
> > @@ -105,6 +110,7 @@ static void
> json_print_section_header(AVTextFormatContext *wctx, const void *dat
> >          wctx->section[wctx->level-1] : NULL;
> >
> >      if (wctx->level && wctx->nb_item[wctx->level-1])
> > +    if (wctx->level && wctx->nb_item[wctx->level - 1])
> >          writer_put_str(wctx, ",\n");
> >
> >      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
> > diff --git a/fftools/textformat/tf_xml.c
> b/fftools/textformat/tf_xml.c
> > index befb39246d..28abfc6400 100644
> > --- a/fftools/textformat/tf_xml.c
> > +++ b/fftools/textformat/tf_xml.c
> > @@ -18,10 +18,7 @@
> >   * 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"
> > diff --git a/fftools/textformat/tw_avio.c
> b/fftools/textformat/tw_avio.c
> > index 6034f74ec9..b1743fb43a 100644
> > --- a/fftools/textformat/tw_avio.c
> > +++ b/fftools/textformat/tw_avio.c
> > @@ -53,7 +53,7 @@ static void io_w8(AVTextWriterContext *wctx, int
> b)
> >  static void io_put_str(AVTextWriterContext *wctx, const char *str)
> >  {
> >      IOWriterContext *ctx = wctx->priv;
> > -    avio_write(ctx->avio_context, str, strlen(str));
> > +    avio_write(ctx->avio_context, (const unsigned char *)str,
> (int)strlen(str));
> >  }
> >
> >  static void io_printf(AVTextWriterContext *wctx, const char *fmt,
> ...)
> > @@ -78,10 +78,12 @@ const AVTextWriter avtextwriter_avio = {
> >
> >  int avtextwriter_create_file(AVTextWriterContext **pwctx, const
> char *output_filename)
> >  {
> > +    if (!pwctx || !output_filename || !output_filename[0])
> > +        return AVERROR(EINVAL);
> 
> You said in [1] that you removed all these impossible checks, yet here
> they are; they are also above.
> 
> [1]: https://ffmpeg.org/pipermail/ffmpeg-devel/2025-April/342317.html

The next commit removes it.


Thanks,
sw



_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 09/10] fftools/graphprint: Add execution graph printing
  2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 09/10] fftools/graphprint: Add execution graph printing softworkz
@ 2025-04-17 18:41     ` Michael Niedermayer
  2025-04-18  2:45       ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Michael Niedermayer @ 2025-04-17 18:41 UTC (permalink / raw)
  To: FFmpeg development discussions and patches


[-- Attachment #1.1: Type: text/plain, Size: 2874 bytes --]

Hi

On Wed, Apr 16, 2025 at 10:12:19AM +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> The key benefits are:
> 
> - Different to other graph printing methods, this is outputting:
>   - all graphs with runtime state
>     (including auto-inserted filters)
>   - each graph with its inputs and outputs
>   - all filters with their in- and output pads
>   - all connections between all input- and output pads
>   - for each connection:
>     - the runtime-negotiated format and media type
>     - the hw context
>     - if video hw context, both: hw pixfmt + sw pixfmt
> - Output can either be printed to stdout or written to specified file
> - Output is machine-readable
> - Use the same output implementation as ffprobe, supporting multiple
>   formats

breaks build on ubuntu mingw

make  -k
CC	fftools/graph/graphprint.o
src/fftools/graph/graphprint.c: In function ‘print_streams’:
src/fftools/graph/graphprint.c:589:24: warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
  589 |     sec_ctx.context_id = "Inputs";
      |                        ^
In file included from src/fftools/graph/graphprint.c:34:
At top level:
src/fftools/ffmpeg_mux.h:126:19: warning: ‘ms_from_ost’ defined but not used [-Wunused-function]
  126 | static MuxStream *ms_from_ost(OutputStream *ost)
      |                   ^~~~~~~~~~~
src/fftools/graph/graphprint.c:1102:1: fatal error: opening dependency file fftools/graph/graphprint.d: No such file or directory
 1102 | }
      | ^
compilation terminated.
make: *** [src/ffbuild/common.mak:81: fftools/graph/graphprint.o] Error 1
CC	fftools/resources/resman.o
src/fftools/resources/resman.c:213:1: fatal error: opening dependency file fftools/resources/resman.d: No such file or directory
  213 | }
      | ^
compilation terminated.
make: *** [src/ffbuild/common.mak:81: fftools/resources/resman.o] Error 1
CC	fftools/resources/graph.html.o
src/fftools/resources/graph.html.c:2:1: fatal error: opening dependency file fftools/resources/graph.html.d: No such file or directory
    2 | const unsigned int ff_graph_html_len = 938;
      | ^~~~~
compilation terminated.
make: *** [src/ffbuild/common.mak:81: fftools/resources/graph.html.o] Error 1
CC	fftools/resources/graph.css.o
src/fftools/resources/graph.css.c:2:1: fatal error: opening dependency file fftools/resources/graph.css.d: No such file or directory
    2 | const unsigned int ff_graph_css_len = 1685;
      | ^~~~~
compilation terminated.
make: *** [src/ffbuild/common.mak:81: fftools/resources/graph.css.o] Error 1
make: Target 'all' not remade because of errors.


[...]
-- 
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

Everything should be made as simple as possible, but not simpler.
-- Albert Einstein

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

[-- Attachment #2: Type: text/plain, Size: 251 bytes --]

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 09/10] fftools/graphprint: Add execution graph printing
  2025-04-17 18:41     ` Michael Niedermayer
@ 2025-04-18  2:45       ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-18  2:45 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Michael Niedermayer
> Sent: Donnerstag, 17. April 2025 20:42
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH v2 09/10] fftools/graphprint: Add
> execution graph printing
> 
> Hi
> 
> On Wed, Apr 16, 2025 at 10:12:19AM +0000, softworkz wrote:
> > From: softworkz <softworkz@hotmail.com>
> >
> > The key benefits are:
> >
> > - Different to other graph printing methods, this is outputting:
> >   - all graphs with runtime state
> >     (including auto-inserted filters)
> >   - each graph with its inputs and outputs
> >   - all filters with their in- and output pads
> >   - all connections between all input- and output pads
> >   - for each connection:
> >     - the runtime-negotiated format and media type
> >     - the hw context
> >     - if video hw context, both: hw pixfmt + sw pixfmt
> > - Output can either be printed to stdout or written to specified
> file
> > - Output is machine-readable
> > - Use the same output implementation as ffprobe, supporting multiple
> >   formats
> 
> breaks build on ubuntu mingw
> 
> make  -k
> CC	fftools/graph/graphprint.o
> src/fftools/graph/graphprint.c: In function ‘print_streams’:
> src/fftools/graph/graphprint.c:589:24: warning: assignment discards
> ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
>   589 |     sec_ctx.context_id = "Inputs";
>       |                        ^
> In file included from src/fftools/graph/graphprint.c:34:
> At top level:
> src/fftools/ffmpeg_mux.h:126:19: warning: ‘ms_from_ost’ defined but
> not used [-Wunused-function]
>   126 | static MuxStream *ms_from_ost(OutputStream *ost)
>       |                   ^~~~~~~~~~~
> src/fftools/graph/graphprint.c:1102:1: fatal error: opening dependency
> file fftools/graph/graphprint.d: No such file or directory
>  1102 | }
>       | ^
> compilation terminated.
> make: *** [src/ffbuild/common.mak:81: fftools/graph/graphprint.o]
> Error 1
> CC	fftools/resources/resman.o
> src/fftools/resources/resman.c:213:1: fatal error: opening dependency
> file fftools/resources/resman.d: No such file or directory
>   213 | }
>       | ^
> compilation terminated.
> make: *** [src/ffbuild/common.mak:81: fftools/resources/resman.o]
> Error 1
> CC	fftools/resources/graph.html.o
> src/fftools/resources/graph.html.c:2:1: fatal error: opening
> dependency file fftools/resources/graph.html.d: No such file or
> directory
>     2 | const unsigned int ff_graph_html_len = 938;
>       | ^~~~~
> compilation terminated.
> make: *** [src/ffbuild/common.mak:81: fftools/resources/graph.html.o]
> Error 1
> CC	fftools/resources/graph.css.o
> src/fftools/resources/graph.css.c:2:1: fatal error: opening dependency
> file fftools/resources/graph.css.d: No such file or directory
>     2 | const unsigned int ff_graph_css_len = 1685;
>       | ^~~~~
> compilation terminated.
> make: *** [src/ffbuild/common.mak:81: fftools/resources/graph.css.o]
> Error 1
> make: Target 'all' not remade because of errors.
> 
> 
> [...]
> --

Oh, those out-of-tree builds again. Should be fixed in V3.

Thank you!
sw




_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat: Quality improvements
  2025-04-16 11:33       ` softworkz .
@ 2025-04-18  2:48         ` softworkz .
  2025-04-18  5:41         ` softworkz .
  1 sibling, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-18  2:48 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> softworkz .
> Sent: Mittwoch, 16. April 2025 13:33
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat:
> Quality improvements
> 
> 
> 
> > -----Original Message-----
> > From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> > Andreas Rheinhardt
> > Sent: Mittwoch, 16. April 2025 12:49
> > To: ffmpeg-devel@ffmpeg.org
> > Subject: Re: [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat:
> > Quality improvements
> >
> > softworkz:
> > > From: softworkz <softworkz@hotmail.com>
> > >
> > > Signed-off-by: softworkz <softworkz@hotmail.com>
> > > ---
> > >  fftools/textformat/avtextformat.c | 111 +++++++++++++++++++------

[..]

> > >
> > >  #include "avtextformat.h"
> > > diff --git a/fftools/textformat/tw_avio.c
> > b/fftools/textformat/tw_avio.c
> > > index 6034f74ec9..b1743fb43a 100644
> > > --- a/fftools/textformat/tw_avio.c
> > > +++ b/fftools/textformat/tw_avio.c
> > > @@ -53,7 +53,7 @@ static void io_w8(AVTextWriterContext *wctx, int
> > b)
> > >  static void io_put_str(AVTextWriterContext *wctx, const char
> *str)
> > >  {
> > >      IOWriterContext *ctx = wctx->priv;
> > > -    avio_write(ctx->avio_context, str, strlen(str));
> > > +    avio_write(ctx->avio_context, (const unsigned char *)str,
> > (int)strlen(str));
> > >  }
> > >
> > >  static void io_printf(AVTextWriterContext *wctx, const char *fmt,
> > ...)
> > > @@ -78,10 +78,12 @@ const AVTextWriter avtextwriter_avio = {
> > >
> > >  int avtextwriter_create_file(AVTextWriterContext **pwctx, const
> > char *output_filename)
> > >  {
> > > +    if (!pwctx || !output_filename || !output_filename[0])
> > > +        return AVERROR(EINVAL);
> >
> > You said in [1] that you removed all these impossible checks, yet
> here
> > they are; they are also above.
> >
> > [1]: https://ffmpeg.org/pipermail/ffmpeg-devel/2025-
> April/342317.html
> 
> The next commit removes it.

In V3, I have squashed it into the previous commit.

Thanks,
sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 00/11] Execution Graph Printing
  2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
                     ` (10 preceding siblings ...)
  2025-04-16 10:21   ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing softworkz .
@ 2025-04-18  2:56   ` ffmpegagent
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 01/11] fftools/textformat: Formatting and whitespace changes softworkz
                       ` (11 more replies)
  11 siblings, 12 replies; 130+ messages in thread
From: ffmpegagent @ 2025-04-18  2:56 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

Shortest cover letter for my longest-running FFmpeg patchset:

 * Apply
 * Build
 * Add the "-sg" switch to any FFmpeg command line
 * Press 'q' when you don't want to wait

SG = Show Graph

Documentation and examples can be found here:

https://github.com/softworkz/ffmpeg_output_apis/wiki


Version Updates
===============


V2
==

 * Rebased on top of Andreas' improvements
 * Applied changes from review (thanks, Andreas)


V3
==

 * Fixed all "new warnings"
 * Fixed out-of-tree building (thanks, Michael)

softworkz (11):
  fftools/textformat: Formatting and whitespace changes
  fftools/textformat: Quality improvements
  fftools/textformat: Introduce common header and deduplicate code
  fftools/tf_internal: Use ac_default_item_name
  fftools/textformat: Add function avtext_print_integer_flags()
  fftools/ffmpeg_filter: Move some declaration to new header file
  avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  fftools/resources: Add resource manager files
  fftools/ffmpeg_mux: Make ms_from_ost() inline
  fftools/graphprint: Add execution graph printing
  fftools/graphprint: Now, make it a Killer-Feature!

 doc/APIchanges                     |    3 +
 doc/ffmpeg.texi                    |   14 +
 ffbuild/common.mak                 |   28 +-
 fftools/Makefile                   |   22 +-
 fftools/ffmpeg.c                   |    4 +
 fftools/ffmpeg.h                   |    4 +
 fftools/ffmpeg_filter.c            |  195 +----
 fftools/ffmpeg_filter.h            |  234 ++++++
 fftools/ffmpeg_mux.h               |    2 +-
 fftools/ffmpeg_opt.c               |   17 +
 fftools/graph/filelauncher.c       |  204 +++++
 fftools/graph/graphprint.c         | 1146 ++++++++++++++++++++++++++++
 fftools/graph/graphprint.h         |   62 ++
 fftools/resources/.gitignore       |    4 +
 fftools/resources/Makefile         |   27 +
 fftools/resources/graph.css        |  353 +++++++++
 fftools/resources/graph.html       |   86 +++
 fftools/resources/resman.c         |  213 ++++++
 fftools/resources/resman.h         |   50 ++
 fftools/textformat/avtextformat.c  |  238 +++---
 fftools/textformat/avtextformat.h  |   53 +-
 fftools/textformat/avtextwriters.h |   11 +-
 fftools/textformat/tf_compact.c    |  121 +--
 fftools/textformat/tf_default.c    |   55 +-
 fftools/textformat/tf_flat.c       |   51 +-
 fftools/textformat/tf_ini.c        |   62 +-
 fftools/textformat/tf_internal.h   |   81 ++
 fftools/textformat/tf_json.c       |   56 +-
 fftools/textformat/tf_mermaid.c    |  658 ++++++++++++++++
 fftools/textformat/tf_mermaid.h    |   41 +
 fftools/textformat/tf_xml.c        |   68 +-
 fftools/textformat/tw_avio.c       |   16 +-
 fftools/textformat/tw_buffer.c     |    7 +-
 fftools/textformat/tw_stdout.c     |    8 +-
 libavfilter/avfilter.c             |    9 +
 libavfilter/avfilter.h             |   12 +
 36 files changed, 3669 insertions(+), 546 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h
 create mode 100644 fftools/graph/filelauncher.c
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h
 create mode 100644 fftools/textformat/tf_internal.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h


base-commit: 7684243fbe6e84fecb4a039195d5fda8a006a2a4
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-66%2Fsoftworkz%2Fsubmit_print_execution_graph-v3
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-66/softworkz/submit_print_execution_graph-v3
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/66

Range-diff vs v2:

  1:  b6468d1a30 =  1:  357859ee56 fftools/textformat: Formatting and whitespace changes
  2:  6568269678 !  2:  9279d2f53d fftools/textformat: Quality improvements
     @@ fftools/textformat/tw_avio.c: const AVTextWriter avtextwriter_avio = {
       
       int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
       {
     -+    if (!pwctx || !output_filename || !output_filename[0])
     ++    if (!output_filename || !output_filename[0])
      +        return AVERROR(EINVAL);
      +
           IOWriterContext *ctx;
  3:  9e77a447b8 !  3:  d6734bd718 fftools/textformat: Introduce common header and deduplicate code
     @@ fftools/textformat/tw_avio.c: static void io_put_str(AVTextWriterContext *wctx,
       }
       
       
     -@@ fftools/textformat/tw_avio.c: const AVTextWriter avtextwriter_avio = {
     - 
     - int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
     - {
     --    if (!pwctx || !output_filename || !output_filename[0])
     -+    if (!output_filename || !output_filename[0])
     -         return AVERROR(EINVAL);
     - 
     -     IOWriterContext *ctx;
      
       ## fftools/textformat/tw_buffer.c ##
      @@ fftools/textformat/tw_buffer.c: static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
  4:  a1b358f5c5 =  4:  8289c0ebf8 fftools/tf_internal: Use ac_default_item_name
  5:  4f6870ed4c =  5:  a3fe7abcfe fftools/textformat: Add function avtext_print_integer_flags()
  6:  9c03e66aea =  6:  5b0ac30814 fftools/ffmpeg_filter: Move some declaration to new header file
  7:  eb54476d00 !  7:  5e3225c9df avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
     @@ doc/APIchanges: The last version increases of all libraries were on 2025-03-28
      +2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
      +  Add avfilter_link_get_hw_frames_ctx().
      +
     - 2025-04-07 - 19e9a203b7 - lavu 60.01.100 - dict.h
     -   Add AV_DICT_DEDUP.
     + 2025-04-16 - c818c67991 - libpostproc 59.1.100 - postprocess.h
     +   Deprecate PP_CPU_CAPS_3DNOW.
       
      
       ## libavfilter/avfilter.c ##
  8:  dac301adba !  8:  9881050f93 fftools/resources: Add resource manager files
     @@ ffbuild/common.mak: else
      +
      +# 2) Gzip the minified CSS
      +%.css.min.gz: %.css.min
     -+	$(M)gzip -nc9 $< > $@
     ++	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) >$@
      +
      +# 3) Convert the gzipped CSS to a .c array
      +%.css.c: %.css.min.gz $(BIN2CEXE)
     -+	$(BIN2C) $< $@ $(subst .,_,$(basename $(notdir $@)))
     ++	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
      +
      +# 4) Gzip the HTML file (no minification needed)
      +%.html.gz: TAG = GZIP
     @@ ffbuild/common.mak: else
      +
      +# 5) Convert the gzipped HTML to a .c array
      +%.html.c: %.html.gz $(BIN2CEXE)
     -+	$(BIN2C) $< $@ $(subst .,_,$(basename $(notdir $@)))
     ++	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
      +
       clean::
       	$(RM) $(BIN2CEXE) $(CLEANSUFFIXES:%=ffbuild/%)
     @@ ffbuild/common.mak: $(TOOLOBJS): | tools
       
       define RULES
      
     + ## fftools/Makefile ##
     +@@ fftools/Makefile: ifdef HAVE_GNU_WINDRES
     + OBJS-$(1) += fftools/fftoolsres.o
     + endif
     + $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
     +-$$(OBJS-$(1)): | fftools fftools/textformat
     ++$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
     + $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
     + $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
     + $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
     +@@ fftools/Makefile: all: $(AVPROGS)
     + fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
     + OUTDIRS += fftools
     + OUTDIRS += fftools/textformat
     ++OUTDIRS += fftools/resources
     + 
     + ifdef AVPROGS
     + install: install-progs install-data
     +
       ## fftools/resources/.gitignore (new) ##
      @@
      +*.html.c
  -:  ---------- >  9:  f639a2e9ff fftools/ffmpeg_mux: Make ms_from_ost() inline
  9:  128ae47177 ! 10:  998d6a70c4 fftools/graphprint: Add execution graph printing
     @@ fftools/Makefile: OBJS-ffprobe +=                       \
       
       OBJS-ffplay += fftools/ffplay_renderer.o
       
     +@@ fftools/Makefile: ifdef HAVE_GNU_WINDRES
     + OBJS-$(1) += fftools/fftoolsres.o
     + endif
     + $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
     +-$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
     ++$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources fftools/graph
     + $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
     + $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
     + $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
     +@@ fftools/Makefile: fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
     + OUTDIRS += fftools
     + OUTDIRS += fftools/textformat
     + OUTDIRS += fftools/resources
     ++OUTDIRS += fftools/graph
     + 
     + ifdef AVPROGS
     + install: install-progs install-data
      
       ## fftools/ffmpeg.c ##
      @@
     @@ fftools/textformat/tf_mermaid.c (new)
      +        AVBPrint css_buf;
      +        const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
      +        char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
     ++        (void)theme_css_er;
      +        ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
      +        av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
      +        av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
     @@ fftools/textformat/tf_mermaid.c (new)
      +
      +            break;
      +        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
     ++
     ++            if (!is_int && str)
      +            {
     -+                char *col_type;
     ++                const char *col_type;
      +
      +                if (key[0] == '_')
      +                    return;
 10:  0f6fd80b25 = 11:  8817dd0991 fftools/graphprint: Now, make it a Killer-Feature!

-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 01/11] fftools/textformat: Formatting and whitespace changes
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
@ 2025-04-18  2:56     ` softworkz
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 02/11] fftools/textformat: Quality improvements softworkz
                       ` (10 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:56 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c  | 104 +++++++++++++++--------------
 fftools/textformat/avtextformat.h  |  16 ++---
 fftools/textformat/avtextwriters.h |  11 ++-
 fftools/textformat/tf_compact.c    |  91 ++++++++++++++-----------
 fftools/textformat/tf_default.c    |  20 +++---
 fftools/textformat/tf_flat.c       |  26 ++++----
 fftools/textformat/tf_ini.c        |  36 +++++-----
 fftools/textformat/tf_json.c       |  10 +--
 fftools/textformat/tf_xml.c        |  30 +++++----
 9 files changed, 183 insertions(+), 161 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 9200b9b1ad..edbcd0b342 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -34,9 +34,9 @@
 #include "libavutil/opt.h"
 #include "avtextformat.h"
 
-#define SECTION_ID_NONE -1
+#define SECTION_ID_NONE (-1)
 
-#define SHOW_OPTIONAL_FIELDS_AUTO       -1
+#define SHOW_OPTIONAL_FIELDS_AUTO      (-1)
 #define SHOW_OPTIONAL_FIELDS_NEVER       0
 #define SHOW_OPTIONAL_FIELDS_ALWAYS      1
 
@@ -64,14 +64,14 @@ static const char *textcontext_get_formatter_name(void *p)
 
 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" },
+      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"}},
+      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 }
 };
 
@@ -125,14 +125,18 @@ void avtext_context_close(AVTextFormatContext **ptctx)
 }
 
 
-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)
+int avtext_context_open(AVTextFormatContext      **ptctx,
+                        const AVTextFormatter     *formatter,
+                        AVTextWriterContext       *writer_context,
+                        const char                *args,
+                        const 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;
@@ -200,7 +204,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash) {
+    if (show_data_hash)
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -211,7 +215,6 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             return ret;
         }
-    }
 
     /* validate replace string */
     {
@@ -224,7 +227,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             if (ret < 0) {
                 AVBPrint bp;
                 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
-                bprint_bytes(&bp, p0, p-p0),
+                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);
@@ -248,15 +251,13 @@ fail:
 }
 
 /* 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_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)
+void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
@@ -272,8 +273,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx,
 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;
+    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]++;
@@ -285,8 +287,7 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
     tctx->level--;
 }
 
-void avtext_print_integer(AVTextFormatContext *tctx,
-                                        const char *key, int64_t val)
+void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
     const struct AVTextFormatSection *section = tctx->section[tctx->level];
 
@@ -324,11 +325,9 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
             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);
+                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);
@@ -340,11 +339,10 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
             av_bprint_append_data(&dstbuf, p0, p-p0);
     }
 
-    if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
+    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);
@@ -352,7 +350,11 @@ end:
 }
 
 struct unit_value {
-    union { double d; int64_t i; } val;
+    union {
+        double  d;
+        int64_t i;
+    } val;
+
     const char *unit;
 };
 
@@ -402,8 +404,9 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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 : "");
+                    prefix_string, tctx->show_value_unit ? uv.unit : "");
     }
 
     return buf;
@@ -427,8 +430,8 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 
     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)))
+            && (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)) {
@@ -440,11 +443,10 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
             if (ret < 0) goto end;
             tctx->formatter->print_string(tctx, key1, val1);
         end:
-            if (ret < 0) {
+            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 {
@@ -457,8 +459,7 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
     return ret;
 }
 
-void avtext_print_rational(AVTextFormatContext *tctx,
-                                         const char *key, AVRational q, char sep)
+void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep)
 {
     char buf[44];
     snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
@@ -466,7 +467,7 @@ void avtext_print_rational(AVTextFormatContext *tctx,
 }
 
 void avtext_print_time(AVTextFormatContext *tctx, const char *key,
-                              int64_t ts, const AVRational *time_base, int is_duration)
+                       int64_t ts, const AVRational *time_base, int is_duration)
 {
     char buf[128];
 
@@ -484,15 +485,14 @@ void avtext_print_time(AVTextFormatContext *tctx, const char *key,
 
 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)) {
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0))
         avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
-    } else {
+    else
         avtext_print_integer(tctx, key, ts);
-    }
 }
 
 void avtext_print_data(AVTextFormatContext *tctx, const char *name,
-                              const uint8_t *data, int size)
+                       const uint8_t *data, int size)
 {
     AVBPrint bp;
     int offset = 0, l, i;
@@ -520,12 +520,13 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 }
 
 void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
-                                   const uint8_t *data, int size)
+                            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));
@@ -551,7 +552,7 @@ void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
             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 --;
+            size--;
         }
         av_bprintf(&bp, "\n");
         offset += offset_add;
@@ -641,7 +642,8 @@ fail:
     return ret;
 }
 
-static const AVTextFormatter *registered_formatters[7+1];
+static const AVTextFormatter *registered_formatters[10 + 1];
+
 static void formatters_register_all(void)
 {
     static int initialized;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c2c56dc1a7..c598af3450 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -86,17 +86,17 @@ typedef struct AVTextFormatter {
 #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
+    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
+    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
+    const AVTextFormatSection *sections; ///< array containing all sections
+    int nb_sections;                   ///< number of sections
 
-    int level;                      ///< current level, starting from 0
+    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];
diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index c99d6b3548..34db3f1832 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -37,11 +37,11 @@ typedef struct AVTextWriter {
     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, ...);
+    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 {
@@ -49,7 +49,6 @@ typedef struct AVTextWriterContext {
     const AVTextWriter *writer;
     const char *name;
     void *priv;                     ///< private data for use by the writer
-
 } AVTextWriterContext;
 
 
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index 31bfc81513..d4ac296a42 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -58,10 +58,10 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 
     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 '\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)
@@ -78,6 +78,7 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 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)
@@ -114,16 +115,16 @@ typedef struct 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},
+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);
@@ -139,10 +140,13 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
     }
     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 {
+    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);
     }
@@ -162,8 +166,8 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
     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))))) {
+            (!(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 */
@@ -171,10 +175,10 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
         AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
 
         compact->nested_section[wctx->level] = 1;
-        compact->has_nested_elems[wctx->level-1] = 1;
+        compact->has_nested_elems[wctx->level - 1] = 1;
 
         av_bprintf(section_pbuf, "%s%s",
-                   wctx->section_pbuf[wctx->level-1].str, element_name);
+                   wctx->section_pbuf[wctx->level - 1].str, element_name);
 
         if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
             // add /TYPE to prefix
@@ -185,30 +189,33 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
                 char c =
                     (*p >= '0' && *p <= '9') ||
                     (*p >= 'a' && *p <= 'z') ||
-                    (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
+                    (*p >= 'A' && *p <= 'Z')
+                    ? (char)(char)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];
+        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])
+        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)))
+            !(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)
 {
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     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)))
+        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
         writer_w8(wctx, '\n');
 }
 
@@ -217,9 +224,12 @@ static void compact_print_str(AVTextFormatContext *wctx, const char *key, const
     CompactContext *compact = wctx->priv;
     AVBPrint buf;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
@@ -229,9 +239,12 @@ static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_
 {
     CompactContext *compact = wctx->priv;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
 }
 
@@ -253,15 +266,15 @@ const AVTextFormatter avtextformatter_compact = {
 #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},
+    { "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);
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 86582829e4..2c5047eafd 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -56,11 +56,11 @@ typedef struct DefaultContext {
 #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},
+    { "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);
@@ -69,7 +69,7 @@ DEFINE_FORMATTER_CLASS(default);
 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++)
+    for (i = 0; src[i] && i < dst_size - 1; i++)
         dst[i] = av_toupper(src[i]);
     dst[i] = 0;
     return dst;
@@ -85,10 +85,10 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 
     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))) {
+        !(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,
+                   wctx->section_pbuf[wctx->level - 1].str,
                    upcase_string(buf, sizeof(buf),
                                  av_x_if_null(section->element_name, section->name)));
     }
@@ -96,7 +96,7 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
     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)))
+    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));
 }
 
@@ -109,7 +109,7 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     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)))
+    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));
 }
 
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index 919d44bc6b..f692971bcc 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -57,12 +57,12 @@ typedef struct 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},
+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);
@@ -126,16 +126,18 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
     av_bprint_clear(buf);
     if (!parent_section)
         return;
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+
+    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))) {
+        !(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];
+            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);
         }
     }
@@ -166,6 +168,6 @@ const AVTextFormatter avtextformatter_flat = {
     .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,
+    .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
index d8099ff92e..88add0819a 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -64,9 +64,9 @@ typedef struct INIContext {
 #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},
+    { "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);
@@ -74,9 +74,9 @@ DEFINE_FORMATTER_CLASS(ini);
 static char *ini_escape_str(AVBPrint *dst, const char *src)
 {
     int i = 0;
-    char c = 0;
+    char c;
 
-    while (c = src[i++]) {
+    while ((c = src[i++])) {
         switch (c) {
         case '\b': av_bprintf(dst, "%s", "\\b"); break;
         case '\f': av_bprintf(dst, "%s", "\\f"); break;
@@ -84,9 +84,11 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
         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);
+        case '#':
+        case '=':
+        case ':':
+            av_bprint_chars(dst, '\\', 1);
+            /* fallthrough */
         default:
             if ((unsigned char)c < 32)
                 av_bprintf(dst, "\\x00%02x", c & 0xff);
@@ -112,23 +114,23 @@ static void ini_print_section_header(AVTextFormatContext *wctx, const void *data
         return;
     }
 
-    if (wctx->nb_item[wctx->level-1])
+    if (wctx->nb_item[wctx->level - 1])
         writer_w8(wctx, '\n');
 
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+    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))) {
+        !(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);
+            unsigned 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, ".%u", n);
         }
     }
 
-    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
         writer_printf(wctx, "[%s]\n", buf->str);
 }
 
@@ -154,6 +156,6 @@ const AVTextFormatter avtextformatter_ini = {
     .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,
+    .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
index c26a912435..b61d3740c6 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -56,9 +56,9 @@ typedef struct 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 },
+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 }
 };
 
@@ -76,8 +76,8 @@ static av_cold int json_init(AVTextFormatContext *wctx)
 
 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};
+    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++) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index 6c89d01e9d..befb39246d 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -58,11 +58,11 @@ typedef struct XMLContext {
 #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},
+    { "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);
@@ -104,8 +104,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 
         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 : "");
+                      xml->fully_qualified ? "ffprobe:" : "",
+                      xml->fully_qualified ? qual : "");
         return;
     }
 
@@ -115,12 +115,13 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
     }
 
     if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
-        wctx->level && wctx->nb_item[wctx->level-1])
+        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_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;
@@ -131,7 +132,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
         }
         writer_printf(wctx, ">\n", section->name);
     } else {
-        XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "<%s ", section->name);
         xml->within_tag = 1;
     }
 }
@@ -148,7 +150,8 @@ static void xml_print_section_footer(AVTextFormatContext *wctx)
         writer_put_str(wctx, "/>\n");
         xml->indent_level--;
     } else {
-        XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "</%s>\n", section->name);
         xml->indent_level--;
     }
 }
@@ -195,7 +198,8 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
     av_bprint_finalize(&buf, NULL);
 }
 
-static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
+static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
     xml_print_value(wctx, key, value, 0, 0);
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 02/11] fftools/textformat: Quality improvements
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 01/11] fftools/textformat: Formatting and whitespace changes softworkz
@ 2025-04-18  2:56     ` softworkz
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 03/11] fftools/textformat: Introduce common header and deduplicate code softworkz
                       ` (9 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:56 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 111 +++++++++++++++++++-----------
 fftools/textformat/avtextformat.h |   6 +-
 fftools/textformat/tf_default.c   |   8 ++-
 fftools/textformat/tf_ini.c       |   2 +-
 fftools/textformat/tf_json.c      |   8 ++-
 fftools/textformat/tf_xml.c       |   3 -
 fftools/textformat/tw_avio.c      |   9 ++-
 7 files changed, 93 insertions(+), 54 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index edbcd0b342..893b11298e 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
 
 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++)
+    for (unsigned i = 0; i < ubuf_size; i++)
         av_bprintf(bp, "%02X", ubuf[i]);
 }
 
@@ -141,7 +140,10 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
     AVTextFormatContext *tctx;
     int i, ret = 0;
 
-    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
+    if (!ptctx || !formatter)
+        return AVERROR(EINVAL);
+
+    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
@@ -213,25 +215,26 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
                     av_log(NULL, AV_LOG_ERROR, " %s", n);
                 av_log(NULL, AV_LOG_ERROR, "\n");
             }
-            return ret;
+            goto fail;
         }
 
     /* validate replace string */
     {
-        const uint8_t *p = tctx->string_validation_replacement;
-        const uint8_t *endp = p + strlen(p);
+        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
+        const uint8_t *endp = p + strlen((const char *)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);
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
                 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;
+                av_bprint_finalize(&bp, NULL);
+                goto fail;
             }
         }
     }
@@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
 
 void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
+    if (section_id < 0 || section_id >= tctx->nb_sections)
+        return;
+
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
 
@@ -272,6 +278,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
 
 void avtext_print_section_footer(AVTextFormatContext *tctx)
 {
+    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
     int section_id = tctx->section[tctx->level]->id;
     int parent_section_id = tctx->level
         ? tctx->section[tctx->level - 1]->id
@@ -289,7 +298,12 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
+
+    if (!key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
+    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);
@@ -299,24 +313,25 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
 
 static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
 {
-    const uint8_t *p, *endp;
+    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
     AVBPrint dstbuf;
+    AVBPrint bp;
     int invalid_chars_nb = 0, ret = 0;
 
+    *dstp = NULL;
     av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
 
-    endp = src + strlen(src);
-    for (p = src; *p;) {
-        uint32_t code;
+    endp = srcp + strlen(src);
+    for (p = srcp; *p;) {
+        int32_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);
+            av_bprint_clear(&bp);
+            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;
         }
 
@@ -336,7 +351,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
         }
 
         if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
-            av_bprint_append_data(&dstbuf, p0, p-p0);
+            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
     }
 
     if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
@@ -346,6 +361,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
 end:
     av_bprint_finalize(&dstbuf, dstp);
+    av_bprint_finalize(&bp, NULL);
     return ret;
 }
 
@@ -358,17 +374,18 @@ struct unit_value {
     const char *unit;
 };
 
-static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
+static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
 {
     double vald;
-    int64_t vali;
+    int64_t vali = 0;
     int show_float = 0;
 
     if (uv.unit == unit_second_str) {
         vald = uv.val.d;
         show_float = 1;
     } else {
-        vald = vali = uv.val.i;
+        vald = (double)uv.val.i;
+        vali = uv.val.i;
     }
 
     if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
@@ -387,17 +404,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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);
+                index = (int64_t)(log2(vald) / 10);
+                index = av_clip64(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);
+                index = (int64_t)(log10(vald) / 3);
+                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
                 vald /= si_prefixes[index].dec_val;
                 prefix_string = si_prefixes[index].dec_str;
             }
-            vali = vald;
+            vali = (int64_t)vald;
         }
 
         if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
@@ -425,9 +442,14 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
 
 int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
     int ret = 0;
 
+    if (!key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return AVERROR(EINVAL);
+
+    section = tctx->section[tctx->level];
+
     if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
         (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
             && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
@@ -469,12 +491,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
 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);
+        char buf[128];
+        double d = av_q2d(*time_base) * ts;
         struct unit_value uv;
         uv.val.d = d;
         uv.unit = unit_second_str;
@@ -495,7 +516,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
                        const uint8_t *data, int size)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -522,25 +544,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 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 };
+    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
+    int len;
 
     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);
+    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
+    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
     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)
+                           uint8_t *data, int size, const char *format,
+                           int columns, int bytes, int offset_add)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
+
+    if (!name || !data || !format || columns <= 0 || bytes <= 0)
+        return;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -606,12 +632,15 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
     AVTextWriterContext *wctx;
     int ret = 0;
 
-    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
+    if (!pwctx || !writer)
+        return AVERROR(EINVAL);
+
+    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
-    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
+    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c598af3450..aea691f351 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -21,9 +21,7 @@
 #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"
@@ -103,7 +101,7 @@ struct AVTextFormatContext {
     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];
+    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
     AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
                                                   ///  used by various formatters
 
@@ -124,7 +122,7 @@ struct AVTextFormatContext {
 #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 2c5047eafd..ad97173b0b 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -68,9 +68,10 @@ 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;
+    unsigned i;
+
     for (i = 0; src[i] && i < dst_size - 1; i++)
-        dst[i] = av_toupper(src[i]);
+        dst[i] = (char)av_toupper(src[i]);
     dst[i] = 0;
     return dst;
 }
@@ -106,6 +107,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     const struct AVTextFormatSection *section = wctx->section[wctx->level];
     char buf[32];
 
+    if (!section)
+        return;
+
     if (def->noprint_wrappers || def->nested_section[wctx->level])
         return;
 
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index 88add0819a..dd77d0e8bf 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
             /* fallthrough */
         default:
             if ((unsigned char)c < 32)
-                av_bprintf(dst, "\\x00%02x", c & 0xff);
+                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
             else
                 av_bprint_chars(dst, c, 1);
             break;
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index b61d3740c6..e86cdbf5d9 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
     static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
     const char *p;
 
+    if (!src) {
+        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source string\n");
+        return NULL;
+    }
+
     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);
+            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
         } else {
             av_bprint_chars(dst, *p, 1);
         }
@@ -105,6 +110,7 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
         wctx->section[wctx->level-1] : NULL;
 
     if (wctx->level && wctx->nb_item[wctx->level-1])
+    if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
     if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index befb39246d..28abfc6400 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -18,10 +18,7 @@
  * 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"
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
index 6034f74ec9..d1b494b7b4 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -53,7 +53,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
 static void io_put_str(AVTextWriterContext *wctx, const char *str)
 {
     IOWriterContext *ctx = wctx->priv;
-    avio_write(ctx->avio_context, str, strlen(str));
+    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
 static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
@@ -78,10 +78,12 @@ const AVTextWriter avtextwriter_avio = {
 
 int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
 {
+    if (!output_filename || !output_filename[0])
+        return AVERROR(EINVAL);
+
     IOWriterContext *ctx;
     int ret;
 
-
     ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
     if (ret < 0)
         return ret;
@@ -103,6 +105,9 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
 
 int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
 {
+    if (!pwctx || !avio_ctx)
+        return AVERROR(EINVAL);
+
     IOWriterContext *ctx;
     int 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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 03/11] fftools/textformat: Introduce common header and deduplicate code
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 01/11] fftools/textformat: Formatting and whitespace changes softworkz
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 02/11] fftools/textformat: Quality improvements softworkz
@ 2025-04-18  2:56     ` softworkz
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 04/11] fftools/tf_internal: Use ac_default_item_name softworkz
                       ` (8 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:56 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextwriters.h |  2 +-
 fftools/textformat/tf_compact.c    | 32 ++++-------
 fftools/textformat/tf_default.c    | 27 +++-------
 fftools/textformat/tf_flat.c       | 25 +++------
 fftools/textformat/tf_ini.c        | 24 +++------
 fftools/textformat/tf_internal.h   | 85 ++++++++++++++++++++++++++++++
 fftools/textformat/tf_json.c       | 38 +++++--------
 fftools/textformat/tf_xml.c        | 35 +++++-------
 fftools/textformat/tw_avio.c       |  7 +--
 fftools/textformat/tw_buffer.c     |  7 +--
 fftools/textformat/tw_stdout.c     |  8 +--
 11 files changed, 149 insertions(+), 141 deletions(-)
 create mode 100644 fftools/textformat/tf_internal.h

diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index 34db3f1832..fd6da747eb 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -41,7 +41,7 @@ typedef struct AVTextWriter {
     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, ...);
+    void (*writer_printf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
 } AVTextWriter;
 
 typedef struct AVTextWriterContext {
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index d4ac296a42..e52888239e 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -28,23 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 
 /* Compact output */
@@ -157,9 +141,12 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
+
     compact->terminate_line[wctx->level] = 1;
     compact->has_nested_elems[wctx->level] = 0;
 
@@ -210,8 +197,11 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
 
 static void compact_print_section_footer(AVTextFormatContext *wctx)
 {
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     CompactContext *compact = wctx->priv;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (!compact->nested_section[wctx->level] &&
         compact->terminate_line[wctx->level] &&
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index ad97173b0b..019bda9d44 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -27,21 +27,7 @@
 #include "avtextformat.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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -80,9 +66,11 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(&wctx->section_pbuf[wctx->level]);
     if (parent_section &&
@@ -104,7 +92,8 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 static void default_print_section_footer(AVTextFormatContext *wctx)
 {
     DefaultContext *def = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
     char buf[32];
 
     if (!section)
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index f692971bcc..d5517f109b 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -28,22 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
-
+#include "tf_internal.h"
 
 /* Flat output */
 
@@ -118,9 +103,11 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     /* build section header */
     av_bprint_clear(buf);
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index dd77d0e8bf..8959785295 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -28,21 +28,7 @@
 
 #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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -104,9 +90,11 @@ 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(buf);
     if (!parent_section) {
diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
new file mode 100644
index 0000000000..7b326328cb
--- /dev/null
+++ b/fftools/textformat/tf_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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
+ */
+
+/**
+ * @file
+ * Internal utilities for text formatters.
+ */
+
+#ifndef FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+#define FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+
+#include "avtextformat.h"
+
+#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                    \
+}
+
+
+/**
+ * Safely validate and access a section at a given level
+ */
+static inline const AVTextFormatSection *tf_get_section(AVTextFormatContext *tfc, int level)
+{
+    if (!tfc || level < 0 || level >= SECTION_MAX_NB_LEVELS || !tfc->section[level]) {
+        if (tfc)
+            av_log(tfc, AV_LOG_ERROR, "Invalid section access at level %d\n", level);
+        return NULL;
+    }
+    return tfc->section[level];
+}
+
+/**
+ * Safely access the parent section
+ */
+static inline const AVTextFormatSection *tf_get_parent_section(AVTextFormatContext *tfc, int level)
+{
+    if (level <= 0)
+        return NULL;
+
+    return tf_get_section(tfc, level - 1);
+}
+
+static inline void writer_w8(AVTextFormatContext *wctx, int b)
+{
+    wctx->writer->writer->writer_w8(wctx->writer, b);
+}
+
+static inline void writer_put_str(AVTextFormatContext *wctx, const char *str)
+{
+    wctx->writer->writer->writer_put_str(wctx->writer, str);
+}
+
+static inline void writer_printf(AVTextFormatContext *wctx, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    wctx->writer->writer->writer_printf(wctx->writer, fmt, args);
+    va_end(args);
+}
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_INTERNAL_H */
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index e86cdbf5d9..8072fa44a4 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -27,22 +27,7 @@
 #include "avtextformat.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                    \
-}
-
+#include "tf_internal.h"
 
 /* JSON output */
 
@@ -103,13 +88,14 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
 
 static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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])
+    if (!section)
+        return;
+
     if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
@@ -143,8 +129,11 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 
 static void json_print_section_footer(AVTextFormatContext *wctx)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         json->indent_level--;
@@ -177,9 +166,8 @@ static inline void json_print_item_str(AVTextFormatContext *wctx,
 
 static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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);
@@ -190,9 +178,8 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 
 static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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))
@@ -216,4 +203,3 @@ const AVTextFormatter avtextformatter_json = {
     .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
index 28abfc6400..6b09e09ab4 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -25,21 +25,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 /* XML output */
 
@@ -90,9 +76,11 @@ static av_cold int xml_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
@@ -138,7 +126,10 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 static void xml_print_section_footer(AVTextFormatContext *wctx)
 {
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
@@ -158,7 +149,10 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
 {
     AVBPrint buf;
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
 
@@ -216,4 +210,3 @@ const AVTextFormatter avtextformatter_xml = {
     .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
index d1b494b7b4..48868ebf5d 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -56,14 +56,11 @@ static void io_put_str(AVTextWriterContext *wctx, const char *str)
     avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
-static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void io_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     IOWriterContext *ctx = wctx->priv;
-    va_list ap;
 
-    va_start(ap, fmt);
-    avio_vprintf(ctx->avio_context, fmt, ap);
-    va_end(ap);
+    avio_vprintf(ctx->avio_context, fmt, vl);
 }
 
 
diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
index f8b38414a6..f861722247 100644
--- a/fftools/textformat/tw_buffer.c
+++ b/fftools/textformat/tw_buffer.c
@@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
     av_bprintf(ctx->buffer, "%s", str);
 }
 
-static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     BufferWriterContext *ctx = wctx->priv;
 
-    va_list vargs;
-    va_start(vargs, fmt);
-    av_vbprintf(ctx->buffer, fmt, vargs);
-    va_end(vargs);
+    av_vbprintf(ctx->buffer, fmt, vl);
 }
 
 
diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
index 23de6f671f..dace55f38a 100644
--- a/fftools/textformat/tw_stdout.c
+++ b/fftools/textformat/tw_stdout.c
@@ -53,13 +53,9 @@ static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
     printf("%s", str);
 }
 
-static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
-    va_list ap;
-
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
+    vprintf(fmt, vl);
 }
 
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 04/11] fftools/tf_internal: Use ac_default_item_name
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
                       ` (2 preceding siblings ...)
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 03/11] fftools/textformat: Introduce common header and deduplicate code softworkz
@ 2025-04-18  2:56     ` softworkz
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 05/11] fftools/textformat: Add function avtext_print_integer_flags() softworkz
                       ` (7 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:56 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/tf_internal.h | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
index 7b326328cb..e145bc83bb 100644
--- a/fftools/textformat/tf_internal.h
+++ b/fftools/textformat/tf_internal.h
@@ -29,13 +29,9 @@
 #include "avtextformat.h"
 
 #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,                  \
+    .item_name  = av_default_item_name,             \
     .option     = name##_options                    \
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 05/11] fftools/textformat: Add function avtext_print_integer_flags()
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
                       ` (3 preceding siblings ...)
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 04/11] fftools/tf_internal: Use ac_default_item_name softworkz
@ 2025-04-18  2:56     ` softworkz
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 06/11] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
                       ` (6 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:56 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This function works analog to the avtext_print_string() which already
has a flags parameter.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 21 +++++++++++++++++++++
 fftools/textformat/avtextformat.h |  2 ++
 2 files changed, 23 insertions(+)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 893b11298e..d2d84c4f1d 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -311,6 +311,27 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
     }
 }
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags)
+{
+    const AVTextFormatSection *section;
+
+    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
+    section = tctx->section[tctx->level];
+
+    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;
+
+    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, *srcp = (const uint8_t *)src;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index aea691f351..16cd9b214f 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -139,6 +139,8 @@ void avtext_print_section_footer(AVTextFormatContext *tctx);
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags);
+
 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);
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 06/11] fftools/ffmpeg_filter: Move some declaration to new header file
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
                       ` (4 preceding siblings ...)
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 05/11] fftools/textformat: Add function avtext_print_integer_flags() softworkz
@ 2025-04-18  2:56     ` softworkz
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 07/11] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
                       ` (5 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:56 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

to allow filtergraph printing to access the information.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_filter.c | 190 +-------------------------------
 fftools/ffmpeg_filter.h | 234 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+), 189 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index d314aec206..eab9487f97 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ffmpeg.h"
+#include "ffmpeg_filter.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -42,44 +43,6 @@
 // FIXME private header, used for mid_pred()
 #include "libavcodec/mathops.h"
 
-typedef struct FilterGraphPriv {
-    FilterGraph      fg;
-
-    // name used for logging
-    char             log_name[32];
-
-    int              is_simple;
-    // true when the filtergraph contains only meta filters
-    // that do not modify the frame data
-    int              is_meta;
-    // source filters are present in the graph
-    int              have_sources;
-    int              disable_conversions;
-
-    unsigned         nb_outputs_done;
-
-    const char      *graph_desc;
-
-    int              nb_threads;
-
-    // frame for temporarily holding output from the filtergraph
-    AVFrame         *frame;
-    // frame for sending output to the encoder
-    AVFrame         *frame_enc;
-
-    Scheduler       *sch;
-    unsigned         sch_idx;
-} FilterGraphPriv;
-
-static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
-{
-    return (FilterGraphPriv*)fg;
-}
-
-static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
-{
-    return (const FilterGraphPriv*)fg;
-}
 
 // data that is local to the filter thread and not visible outside of it
 typedef struct FilterGraphThread {
@@ -102,157 +65,6 @@ typedef struct FilterGraphThread {
     uint8_t         *eof_out;
 } FilterGraphThread;
 
-typedef struct InputFilterPriv {
-    InputFilter         ifilter;
-
-    InputFilterOptions  opts;
-
-    int                 index;
-
-    AVFilterContext    *filter;
-
-    // used to hold submitted input
-    AVFrame            *frame;
-
-    /* for filters that are not yet bound to an input stream,
-     * this stores the input linklabel, if any */
-    uint8_t            *linklabel;
-
-    // filter data type
-    enum AVMediaType    type;
-    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
-    // same as type otherwise
-    enum AVMediaType    type_src;
-
-    int                 eof;
-    int                 bound;
-    int                 drop_warned;
-    uint64_t            nb_dropped;
-
-    // parameters configured for this input
-    int                 format;
-
-    int                 width, height;
-    AVRational          sample_aspect_ratio;
-    enum AVColorSpace   color_space;
-    enum AVColorRange   color_range;
-
-    int                 sample_rate;
-    AVChannelLayout     ch_layout;
-
-    AVRational          time_base;
-
-    AVFrameSideData   **side_data;
-    int                 nb_side_data;
-
-    AVFifo             *frame_queue;
-
-    AVBufferRef        *hw_frames_ctx;
-
-    int                 displaymatrix_present;
-    int                 displaymatrix_applied;
-    int32_t             displaymatrix[9];
-
-    int                 downmixinfo_present;
-    AVDownmixInfo       downmixinfo;
-
-    struct {
-        AVFrame *frame;
-
-        int64_t last_pts;
-        int64_t end_pts;
-
-        /// marks if sub2video_update should force an initialization
-        unsigned int initialize;
-    } sub2video;
-} InputFilterPriv;
-
-static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
-{
-    return (InputFilterPriv*)ifilter;
-}
-
-typedef struct FPSConvContext {
-    AVFrame          *last_frame;
-    /* number of frames emitted by the video-encoding sync code */
-    int64_t           frame_number;
-    /* history of nb_frames_prev, i.e. the number of times the
-     * previous frame was duplicated by vsync code in recent
-     * do_video_out() calls */
-    int64_t           frames_prev_hist[3];
-
-    uint64_t          dup_warning;
-
-    int               last_dropped;
-    int               dropped_keyframe;
-
-    enum VideoSyncMethod vsync_method;
-
-    AVRational        framerate;
-    AVRational        framerate_max;
-    const AVRational *framerate_supported;
-    int               framerate_clip;
-} FPSConvContext;
-
-typedef struct OutputFilterPriv {
-    OutputFilter            ofilter;
-
-    int                     index;
-
-    void                   *log_parent;
-    char                    log_name[32];
-
-    char                   *name;
-
-    AVFilterContext        *filter;
-
-    /* desired output stream properties */
-    int                     format;
-    int                     width, height;
-    int                     sample_rate;
-    AVChannelLayout         ch_layout;
-    enum AVColorSpace       color_space;
-    enum AVColorRange       color_range;
-
-    AVFrameSideData       **side_data;
-    int                     nb_side_data;
-
-    // time base in which the output is sent to our downstream
-    // does not need to match the filtersink's timebase
-    AVRational              tb_out;
-    // at least one frame with the above timebase was sent
-    // to our downstream, so it cannot change anymore
-    int                     tb_out_locked;
-
-    AVRational              sample_aspect_ratio;
-
-    AVDictionary           *sws_opts;
-    AVDictionary           *swr_opts;
-
-    // those are only set if no format is specified and the encoder gives us multiple options
-    // They point directly to the relevant lists of the encoder.
-    const int              *formats;
-    const AVChannelLayout  *ch_layouts;
-    const int              *sample_rates;
-    const enum AVColorSpace *color_spaces;
-    const enum AVColorRange *color_ranges;
-
-    AVRational              enc_timebase;
-    int64_t                 trim_start_us;
-    int64_t                 trim_duration_us;
-    // offset for output timestamps, in AV_TIME_BASE_Q
-    int64_t                 ts_offset;
-    int64_t                 next_pts;
-    FPSConvContext          fps;
-
-    unsigned                flags;
-} OutputFilterPriv;
-
-static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
-{
-    return (OutputFilterPriv*)ofilter;
-}
-
 typedef struct FilterCommand {
     char *target;
     char *command;
diff --git a/fftools/ffmpeg_filter.h b/fftools/ffmpeg_filter.h
new file mode 100644
index 0000000000..94b94beece
--- /dev/null
+++ b/fftools/ffmpeg_filter.h
@@ -0,0 +1,234 @@
+/*
+ * 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_FFMPEG_FILTER_H
+#define FFTOOLS_FFMPEG_FILTER_H
+
+#include "ffmpeg.h"
+
+#include <stdint.h>
+
+#include "ffmpeg_sched.h"
+#include "sync_queue.h"
+
+#include "libavfilter/avfilter.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/dict.h"
+#include "libavutil/fifo.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/downmix_info.h"
+
+typedef struct FilterGraphPriv {
+    FilterGraph      fg;
+
+    // name used for logging
+    char             log_name[32];
+
+    int              is_simple;
+    // true when the filtergraph contains only meta filters
+    // that do not modify the frame data
+    int              is_meta;
+    // source filters are present in the graph
+    int              have_sources;
+    int              disable_conversions;
+
+    unsigned         nb_outputs_done;
+
+    const char      *graph_desc;
+
+    int              nb_threads;
+
+    // frame for temporarily holding output from the filtergraph
+    AVFrame         *frame;
+    // frame for sending output to the encoder
+    AVFrame         *frame_enc;
+
+    Scheduler       *sch;
+    unsigned         sch_idx;
+
+    AVBPrint graph_print_buf;
+
+} FilterGraphPriv;
+
+static inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
+{
+    return (FilterGraphPriv*)fg;
+}
+
+static inline const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
+{
+    return (const FilterGraphPriv*)fg;
+}
+
+typedef struct InputFilterPriv {
+    InputFilter         ifilter;
+
+    InputFilterOptions  opts;
+
+    int                 index;
+
+    AVFilterContext    *filter;
+
+    // used to hold submitted input
+    AVFrame            *frame;
+
+    /* for filters that are not yet bound to an input stream,
+     * this stores the input linklabel, if any */
+    uint8_t            *linklabel;
+
+    // filter data type
+    enum AVMediaType    type;
+    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
+    // same as type otherwise
+    enum AVMediaType    type_src;
+
+    int                 eof;
+    int                 bound;
+    int                 drop_warned;
+    uint64_t            nb_dropped;
+
+    // parameters configured for this input
+    int                 format;
+
+    int                 width, height;
+    AVRational          sample_aspect_ratio;
+    enum AVColorSpace   color_space;
+    enum AVColorRange   color_range;
+
+    int                 sample_rate;
+    AVChannelLayout     ch_layout;
+
+    AVRational          time_base;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
+    AVFifo             *frame_queue;
+
+    AVBufferRef        *hw_frames_ctx;
+
+    int                 displaymatrix_present;
+    int                 displaymatrix_applied;
+    int32_t             displaymatrix[9];
+
+    int                 downmixinfo_present;
+    AVDownmixInfo       downmixinfo;
+
+    struct {
+        AVFrame *frame;
+
+        int64_t last_pts;
+        int64_t end_pts;
+
+        /// marks if sub2video_update should force an initialization
+        unsigned int initialize;
+    } sub2video;
+} InputFilterPriv;
+
+static inline InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
+{
+    return (InputFilterPriv*)ifilter;
+}
+
+typedef struct FPSConvContext {
+    AVFrame          *last_frame;
+    /* number of frames emitted by the video-encoding sync code */
+    int64_t           frame_number;
+    /* history of nb_frames_prev, i.e. the number of times the
+     * previous frame was duplicated by vsync code in recent
+     * do_video_out() calls */
+    int64_t           frames_prev_hist[3];
+
+    uint64_t          dup_warning;
+
+    int               last_dropped;
+    int               dropped_keyframe;
+
+    enum VideoSyncMethod vsync_method;
+
+    AVRational        framerate;
+    AVRational        framerate_max;
+    const AVRational *framerate_supported;
+    int               framerate_clip;
+} FPSConvContext;
+
+
+typedef struct OutputFilterPriv {
+    OutputFilter            ofilter;
+
+    int                     index;
+
+    void                   *log_parent;
+    char                    log_name[32];
+
+    char                   *name;
+
+    AVFilterContext        *filter;
+
+    /* desired output stream properties */
+    int                     format;
+    int                     width, height;
+    int                     sample_rate;
+    AVChannelLayout         ch_layout;
+    enum AVColorSpace       color_space;
+    enum AVColorRange       color_range;
+
+    AVFrameSideData       **side_data;
+    int                     nb_side_data;
+
+    // time base in which the output is sent to our downstream
+    // does not need to match the filtersink's timebase
+    AVRational              tb_out;
+    // at least one frame with the above timebase was sent
+    // to our downstream, so it cannot change anymore
+    int                     tb_out_locked;
+
+    AVRational              sample_aspect_ratio;
+
+    AVDictionary           *sws_opts;
+    AVDictionary           *swr_opts;
+
+    // those are only set if no format is specified and the encoder gives us multiple options
+    // They point directly to the relevant lists of the encoder.
+    const int              *formats;
+    const AVChannelLayout  *ch_layouts;
+    const int              *sample_rates;
+    const enum AVColorSpace *color_spaces;
+    const enum AVColorRange *color_ranges;
+
+    AVRational              enc_timebase;
+    int64_t                 trim_start_us;
+    int64_t                 trim_duration_us;
+    // offset for output timestamps, in AV_TIME_BASE_Q
+    int64_t                 ts_offset;
+    int64_t                 next_pts;
+    FPSConvContext          fps;
+
+    unsigned                flags;
+} OutputFilterPriv;
+
+static inline OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
+{
+    return (OutputFilterPriv*)ofilter;
+}
+
+#endif /* FFTOOLS_FFMPEG_FILTER_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 07/11] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
                       ` (5 preceding siblings ...)
  2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 06/11] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-04-18  2:57     ` softworkz
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 08/11] fftools/resources: Add resource manager files softworkz
                       ` (4 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:57 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/APIchanges         |  3 +++
 libavfilter/avfilter.c |  9 +++++++++
 libavfilter/avfilter.h | 12 ++++++++++++
 3 files changed, 24 insertions(+)

diff --git a/doc/APIchanges b/doc/APIchanges
index 22aa6fa5c7..4f60bbbe3d 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
+  Add avfilter_link_get_hw_frames_ctx().
+
 2025-04-16 - c818c67991 - libpostproc 59.1.100 - postprocess.h
   Deprecate PP_CPU_CAPS_3DNOW.
 
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index 64c1075c40..c76d43a215 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -989,6 +989,15 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx)
     return pads[pad_idx].type;
 }
 
+AVBufferRef *avfilter_link_get_hw_frames_ctx(AVFilterLink *link)
+{
+    FilterLink *plink = ff_filter_link(link);
+    if (plink->hw_frames_ctx)
+        return av_buffer_ref(plink->hw_frames_ctx);
+
+    return NULL;
+}
+
 static int default_filter_frame(AVFilterLink *link, AVFrame *frame)
 {
     return ff_filter_frame(link->dst->outputs[0], frame);
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index a89d3cf658..f85929dc5c 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -96,6 +96,18 @@ const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
  */
 enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
 
+/**
+ * Get the hardware frames context of a filter link.
+ *
+ * @param link an AVFilterLink
+ *
+ * @return a ref-counted copy of the link's hw_frames_ctx field if there is
+ *         a hardware frames context associated with the link or NULL otherwise.
+ *         The returned AVBufferRef needs to be released with av_buffer_unref()
+ *         when it is no longer used.
+ */
+AVBufferRef* avfilter_link_get_hw_frames_ctx(AVFilterLink *link);
+
 /**
  * Lists of formats / etc. supported by an end of a link.
  *
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 08/11] fftools/resources: Add resource manager files
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
                       ` (6 preceding siblings ...)
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 07/11] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
@ 2025-04-18  2:57     ` softworkz
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 09/11] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
                       ` (3 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:57 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 ffbuild/common.mak           |  28 ++-
 fftools/Makefile             |   3 +-
 fftools/resources/.gitignore |   4 +
 fftools/resources/Makefile   |  27 +++
 fftools/resources/graph.css  | 353 +++++++++++++++++++++++++++++++++++
 fftools/resources/graph.html |  86 +++++++++
 fftools/resources/resman.c   | 213 +++++++++++++++++++++
 fftools/resources/resman.h   |  50 +++++
 8 files changed, 762 insertions(+), 2 deletions(-)
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h

diff --git a/ffbuild/common.mak b/ffbuild/common.mak
index ca45a0f368..6717092d44 100644
--- a/ffbuild/common.mak
+++ b/ffbuild/common.mak
@@ -139,6 +139,32 @@ else
 	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
 endif
 
+# 1) Preprocess CSS to a minified version
+%.css.min: %.css
+	# Must start with a tab in the real Makefile
+	sed 's!/\\*.*\\*/!!g' $< \
+	| tr '\n' ' ' \
+	| tr -s ' ' \
+	| sed 's/^ //; s/ $$//' \
+	> $@
+
+# 2) Gzip the minified CSS
+%.css.min.gz: %.css.min
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) >$@
+
+# 3) Convert the gzipped CSS to a .c array
+%.css.c: %.css.min.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
+# 4) Gzip the HTML file (no minification needed)
+%.html.gz: TAG = GZIP
+%.html.gz: %.html
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) > $@
+
+# 5) Convert the gzipped HTML to a .c array
+%.html.c: %.html.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
 clean::
 	$(RM) $(BIN2CEXE) $(CLEANSUFFIXES:%=ffbuild/%)
 
@@ -214,7 +240,7 @@ $(TOOLOBJS): | tools
 
 OUTDIRS := $(OUTDIRS) $(dir $(OBJS) $(HOBJS) $(HOSTOBJS) $(SLIBOBJS) $(SHLIBOBJS) $(STLIBOBJS) $(TESTOBJS))
 
-CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
+CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *.html.gz *.html.c *.css.gz *.css.c  *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
 LIBSUFFIXES       = *.a *.lib *.so *.so.* *.dylib *.dll *.def *.dll.a
 
 define RULES
diff --git a/fftools/Makefile b/fftools/Makefile
index e9c9891c34..a30bec889e 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -42,7 +42,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -56,6 +56,7 @@ all: $(AVPROGS)
 fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
+OUTDIRS += fftools/resources
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/resources/.gitignore b/fftools/resources/.gitignore
new file mode 100644
index 0000000000..5f496535a6
--- /dev/null
+++ b/fftools/resources/.gitignore
@@ -0,0 +1,4 @@
+*.html.c
+*.css.c
+*.html.gz
+*.css.gz
diff --git a/fftools/resources/Makefile b/fftools/resources/Makefile
new file mode 100644
index 0000000000..f3a0d0a970
--- /dev/null
+++ b/fftools/resources/Makefile
@@ -0,0 +1,27 @@
+clean::
+	$(RM) $(CLEANSUFFIXES:%=fftools/resources/%)
+
+
+HTML_RESOURCES := fftools/resources/graph.html \
+
+# .html => (gzip) .html.gz => (bin2c) .html.c => (cc) .o
+HTML_RESOURCES_GZ := $(HTML_RESOURCES:.html=.html.gz)
+HTML_RESOURCES_C := $(HTML_RESOURCES_GZ:.html.gz=.html.c)
+HTML_RESOURCES_OBJS := $(HTML_RESOURCES_C:.c=.o)
+
+CSS_RESOURCES := fftools/resources/graph.css   \
+
+# .css => (sh) .css.min  => (gzip) .css.min.gz => (bin2c) .css.c => (cc) .o
+CSS_RESOURCES_MIN := $(CSS_RESOURCES:.css=.css.min)
+CSS_RESOURCES_GZ := $(CSS_RESOURCES_MIN:.css.min=.css.min.gz)
+CSS_RESOURCES_C := $(CSS_RESOURCES_GZ:.css.min.gz=.css.c)
+CSS_RESOURCES_OBJS := $(CSS_RESOURCES_C:.c=.o)
+
+# Uncomment to prevent deletion
+#.PRECIOUS: %.css.c %.css.min %.css.gz  %.css.min.gz
+
+OBJS-resman +=                  \
+    fftools/resources/resman.o          \
+    $(HTML_RESOURCES_OBJS)      \
+    $(CSS_RESOURCES_OBJS)       \
+
diff --git a/fftools/resources/graph.css b/fftools/resources/graph.css
new file mode 100644
index 0000000000..ab480673ab
--- /dev/null
+++ b/fftools/resources/graph.css
@@ -0,0 +1,353 @@
+/* Variables */
+.root {
+    --ff-colvideo: #6eaa7b;
+    --ff-colaudio: #477fb3;
+    --ff-colsubtitle: #ad76ab;
+    --ff-coltext: #666;
+}
+
+/* Common & Misc */
+.ff-inputfiles rect, .ff-outputfiles rect, .ff-inputstreams rect, .ff-outputstreams rect, .ff-decoders rect, .ff-encoders rect {
+    stroke-width: 0;
+    stroke: transparent;
+    filter: none !important;
+    fill: transparent !important;
+    display: none !important;
+}
+
+.cluster span {
+    color: var(--ff-coltext);
+}
+
+.cluster rect {
+    stroke: #dfdfdf !important;
+    transform: translateY(-2.3rem);
+    filter: drop-shadow(1px 2px 2px rgba(185,185,185,0.2)) !important;
+    rx: 8;
+    ry: 8;
+}
+
+.cluster-label {
+    font-size: 1.1rem;
+}
+
+    .cluster-label .nodeLabel {
+        display: block;
+        font-weight: 500;
+        color: var(--ff-coltext);
+    }
+
+    .cluster-label div {
+        max-width: unset !important;
+        padding: 3px;
+    }
+
+    .cluster-label foreignObject {
+        transform: translateY(-0.7rem);
+    }
+
+/* Input and output files */
+.node.ff-inputfile .label foreignObject, .node.ff-outputfile .label foreignObject {
+    overflow: visible;
+}
+
+.cluster.ff-inputfile .cluster-label foreignObject div:not(foreignObject div div), .cluster.ff-outputfile .cluster-label foreignObject div:not(foreignObject div div) {
+    display: table !important;
+}
+
+.nodeLabel div.ff-inputfile, .nodeLabel div.ff-outputfile {
+    font-size: 1.1rem;
+    font-weight: 500;
+    min-width: 14rem;
+    width: 100%;
+    display: flex;
+    color: var(--ff-coltext);
+    margin-top: 0.1rem;
+    line-height: 1.35;
+    padding-bottom: 1.9rem;
+}
+
+.nodeLabel div.ff-outputfile {
+    flex-direction: row-reverse;
+}
+
+.ff-inputfile .index, .ff-outputfile .index {
+    order: 2;
+    color: var(--ff-coltext);
+    text-align: center;
+    border-radius: 0.45rem;
+    border: 0.18em solid #666666db;
+    font-weight: 600;
+    padding: 0 0.3em;
+    opacity: 0.8;
+}
+
+    .ff-inputfile .index::before {
+        content: 'In ';
+    }
+
+    .ff-outputfile .index::before {
+        content: 'Out ';
+    }
+
+.ff-inputfile .demuxer_name, .ff-outputfile .muxer_name {
+    flex: 1;
+    order: 1;
+    font-size: 0.9rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: center;
+    max-width: 8rem;
+    align-content: center;
+    margin: 0.2rem 0.4rem 0 0.4rem;
+}
+
+.ff-inputfile .file_extension, .ff-outputfile .file_extension {
+    order: 0;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.45rem;
+    font-weight: 600;
+    padding: 0 0.4em;
+    align-content: center;
+    opacity: 0.8;
+}
+
+.ff-inputfile .url, .ff-outputfile .url {
+    order: 4;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0.75rem;
+    font-size: 0.7rem;
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin: 0 0.3rem;
+    direction: rtl;
+    color: #999;
+}
+
+.cluster.ff-inputfile rect, .cluster.ff-outputfile rect {
+    transform: translateY(-1.8rem);
+    fill: url(#ff-radgradient);
+}
+
+/* Input and output streams */
+.node.ff-inputstream rect, .node.ff-outputstream rect {
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    fill: white;
+    stroke: #e5e5e5 !important;
+    height: 2.7rem;
+    transform: translateY(0.2rem);
+    filter: none;
+    rx: 3;
+    ry: 3;
+}
+
+.node.ff-inputstream .label foreignObject, .node.ff-outputstream .label foreignObject {
+    transform: translateY(-0.2%);
+    overflow: visible;
+}
+
+    .node.ff-inputstream .label foreignObject div:not(foreignObject div div), .node.ff-outputstream .label foreignObject div:not(foreignObject div div) {
+        display: block !important;
+        line-height: 1.5 !important;
+    }
+
+.nodeLabel div.ff-inputstream, .nodeLabel div.ff-outputstream {
+    font-size: 1.0rem;
+    font-weight: 500;
+    min-width: 12rem;
+    width: 100%;
+    display: flex;
+}
+
+.nodeLabel div.ff-outputstream {
+    flex-direction: row-reverse;
+}
+
+.ff-inputstream .name, .ff-outputstream .name {
+    flex: 1;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: left;
+    align-content: center;
+    margin-bottom: -0.15rem;
+}
+
+.ff-inputstream .index, .ff-outputstream .index {
+    flex: 0 0 1.4rem;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.3rem;
+    font-weight: 600;
+    margin-right: -0.3rem;
+    margin-left: 0.4rem;
+    opacity: 0.8;
+}
+
+.ff-outputstream .index {
+    margin-right: 0.6rem;
+    margin-left: -0.4rem;
+}
+
+.ff-inputstream::before, .ff-outputstream::before {
+    font-variant-emoji: text;
+    flex: 0 0 2rem;
+    margin-left: -0.8rem;
+    margin-right: 0.2rem;
+}
+
+.ff-outputstream::before {
+    margin-left: 0.2rem;
+    margin-right: -0.6rem;
+}
+
+.ff-inputstream.video::before, .ff-outputstream.video::before {
+    content: '\239A';
+    color: var(--ff-colvideo);
+    font-size: 2.25rem;
+    line-height: 0.5;
+    font-weight: bold;
+}
+
+.ff-inputstream.audio::before, .ff-outputstream.audio::before {
+    content: '\1F39D';
+    color: var(--ff-colaudio);
+    font-size: 1.75rem;
+    line-height: 0.9;
+}
+
+.ff-inputstream.subtitle::before, .ff-outputstream.subtitle::before {
+    content: '\1AC';
+    color: var(--ff-colsubtitle);
+    font-size: 1.2rem;
+    line-height: 1.1;
+    transform: scaleX(1.5);
+    margin-top: 0.050rem;
+}
+
+.ff-inputstream.attachment::before, .ff-outputstream.attachment::before {
+    content: '\1F4CE';
+    font-size: 1.3rem;
+    line-height: 1.15;
+}
+
+.ff-inputstream.data::before, .ff-outputstream.data::before {
+    content: '\27E8\2219\2219\2219\27E9';
+    font-size: 1.15rem;
+    line-height: 1.17;
+    letter-spacing: -0.3px;
+}
+
+/* Filter Graphs */
+.cluster.ff-filters rect {
+    stroke-dasharray: 6 !important;
+    stroke-width: 1.3px;
+    stroke: #d1d1d1 !important;
+    filter: none !important;
+}
+
+.cluster.ff-filters div.ff-filters .id {
+    display: none;
+}
+
+.cluster.ff-filters div.ff-filters .name {
+    margin-right: 0.5rem;
+    font-size: 0.9rem;
+}
+
+.cluster.ff-filters div.ff-filters .description {
+    font-weight: 400;
+    font-size: 0.75rem;
+    vertical-align: middle;
+    color: #777;
+    font-family: Cascadia Code, Lucida Console, monospace;
+}
+
+/* Filter Shapes */
+.node.ff-filter rect {
+    rx: 10;
+    ry: 10;
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.node.ff-filter .label foreignObject {
+    transform: translateY(-0.4rem);
+    overflow: visible;
+}
+
+.nodeLabel div.ff-filter {
+    font-size: 1.0rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    min-width: 5.5rem;
+    margin-bottom: 0.5rem;
+}
+
+    .nodeLabel div.ff-filter span {
+        color: inherit;
+    }
+
+/* Decoders & Encoders */
+.node.ff-decoder rect, .node.ff-encoder rect {
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.nodeLabel div.ff-decoder, .nodeLabel div.ff-encoder {
+    font-size: 0.85rem;
+    font-weight: 500;
+    min-width: 3.5rem;
+}
+
+/* Links and Arrows */
+path.flowchart-link[id|='video'] {
+    stroke: var(--ff-colvideo);
+}
+
+path.flowchart-link[id|='audio'] {
+    stroke: var(--ff-colaudio);
+}
+
+path.flowchart-link[id|='subtitle'] {
+    stroke: var(--ff-colsubtitle);
+}
+
+marker.marker path {
+    fill: context-stroke;
+}
+
+.edgeLabel foreignObject {
+    transform: translateY(-1rem);
+}
+
+.edgeLabel p {
+    background: transparent;
+    white-space: nowrap;
+    margin: 1rem 0.5rem !important;
+    font-weight: 500;
+    color: var(--ff-coltext);
+}
+
+.edgeLabel, .labelBkg {
+    background: transparent;
+}
+
+.edgeLabels .edgeLabel * {
+    font-size: 0.8rem;
+}
diff --git a/fftools/resources/graph.html b/fftools/resources/graph.html
new file mode 100644
index 0000000000..cd94276fd4
--- /dev/null
+++ b/fftools/resources/graph.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <title>FFmpeg Graph</title>
+</head>
+<body>
+<style>
+    html {
+        color: #666;
+        font-family: Roboto;
+        height: 100%;
+    }
+
+    body {
+        background-color: #f9f9f9;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        margin: 0;
+        padding: 1.7rem 1.7rem 3.5rem 1.7rem;
+    }
+
+    div#banner {
+        align-items: center;
+        display: flex;
+        flex-direction: row;
+        margin-bottom: 1.5rem;
+        margin-left: 0.6vw;
+    }
+
+    div#header {
+        aspect-ratio: 1/1;
+        background-image: url(https://trac.ffmpeg.org/ffmpeg-logo.png);
+        background-size: cover;
+        width: 1.6rem;
+    }
+
+    h1 {
+        font-size: 1.2rem;
+        margin: 0 0.5rem;
+    }
+
+    pre.mermaid {
+        align-items: center;
+        background-color: white;
+        box-shadow: 2px 2px 25px 0px #00000010;
+        color: transparent;
+        display: flex;
+        flex: 1;
+        justify-content: center;
+        margin: 0;
+        overflow: overlay;
+    }
+
+    pre.mermaid svg {
+        height: auto;
+        margin: 0;
+        max-width: unset !important;
+        width: auto;
+    }
+
+    pre.mermaid svg * {
+        user-select: none;
+    }
+</style>
+<div id="banner">
+    <div id="header"></div>
+    <h1>FFmpeg Execution Graph</h1>
+</div>
+<pre class="mermaid">
+__###__
+</pre>
+<script type="module">
+        import vanillaJsWheelZoom from 'https://cdn.jsdelivr.net/npm/vanilla-js-wheel-zoom@9.0.4/+esm';
+        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
+        function initViewer() {
+            var element = document.querySelector('.mermaid svg')
+            vanillaJsWheelZoom.create('pre.mermaid svg', { type: 'html', smoothTimeDrag: 0, width: element.clientWidth, height: element.clientHeight, maxScale: 3 });
+        }
+        mermaid.initialize({ startOnLoad: false }); 
+        document.fonts.ready.then(() => { mermaid.run({ querySelector: '.mermaid', postRenderCallback: initViewer }); });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/fftools/resources/resman.c b/fftools/resources/resman.c
new file mode 100644
index 0000000000..488aaeecf6
--- /dev/null
+++ b/fftools/resources/resman.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <zlib.h>
+#include "resman.h"
+#include <libavformat/url.h>
+#include "fftools/ffmpeg_filter.h"
+#include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+
+extern const unsigned char ff_graph_html_data[];
+extern const unsigned int ff_graph_html_len;
+
+extern const unsigned char ff_graph_css_data[];
+extern const unsigned ff_graph_css_len;
+
+static const FFResourceDefinition resource_definitions[] = {
+    [FF_RESOURCE_GRAPH_CSS]   = { FF_RESOURCE_GRAPH_CSS,   "graph.css",   &ff_graph_css_data[0],   &ff_graph_css_len   },
+    [FF_RESOURCE_GRAPH_HTML]  = { FF_RESOURCE_GRAPH_HTML,  "graph.html",  &ff_graph_html_data[0],  &ff_graph_html_len  },
+};
+
+
+static const AVClass resman_class = {
+    .class_name = "ResourceManager",
+};
+
+typedef struct ResourceManagerContext {
+    const AVClass *class;
+    AVDictionary *resource_dic;
+} ResourceManagerContext;
+
+static AVMutex mutex = AV_MUTEX_INITIALIZER;
+
+ResourceManagerContext *resman_ctx = NULL;
+
+
+static int decompress_gzip(ResourceManagerContext *ctx, uint8_t *in, unsigned in_len, char **out, size_t *out_len)
+{
+    z_stream strm;
+    unsigned chunk = 65534;
+    int ret;
+    uint8_t *buf;
+
+    *out = NULL;
+    memset(&strm, 0, sizeof(strm));
+
+    // Allocate output buffer with extra byte for null termination
+    buf = (uint8_t *)av_mallocz(chunk + 1);
+    if (!buf) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to allocate decompression buffer\n");
+        return AVERROR(ENOMEM);
+    }
+
+    // 15 + 16 tells zlib to detect GZIP or zlib automatically
+    ret = inflateInit2(&strm, 15 + 16);
+    if (ret != Z_OK) {
+        av_log(ctx, AV_LOG_ERROR, "Error during zlib initialization: %s\n", strm.msg);
+        av_free(buf);
+        return AVERROR(ENOSYS);
+    }
+
+    strm.avail_in  = in_len;
+    strm.next_in   = in;
+    strm.avail_out = chunk;
+    strm.next_out  = buf;
+
+    ret = inflate(&strm, Z_FINISH);
+    if (ret != Z_OK && ret != Z_STREAM_END) {
+        av_log(ctx, AV_LOG_ERROR, "Inflate failed: %d, %s\n", ret, strm.msg);
+        inflateEnd(&strm);
+        av_free(buf);
+        return (ret == Z_STREAM_END) ? Z_OK : ((ret == Z_OK) ? Z_BUF_ERROR : ret);
+    }
+
+    if (strm.avail_out == 0) {
+        // TODO: Error or loop decoding?
+        av_log(ctx, AV_LOG_WARNING, "Decompression buffer may be too small\n");
+    }
+
+    *out_len = chunk - strm.avail_out;
+    buf[*out_len] = 0; // Ensure null termination
+
+    inflateEnd(&strm);
+    *out = (char *)buf;
+    return Z_OK;
+}
+
+static ResourceManagerContext *get_resman_context(void)
+{
+    ResourceManagerContext *res = resman_ctx;
+
+    ff_mutex_lock(&mutex);
+
+    if (res)
+        goto end;
+
+    res = av_mallocz(sizeof(ResourceManagerContext));
+    if (!res) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to allocate resource manager context\n");
+        goto end;
+    }
+
+    res->class = &resman_class;
+    resman_ctx = res;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
+
+
+void ff_resman_uninit(void)
+{
+    ff_mutex_lock(&mutex);
+
+    if (resman_ctx) {
+        if (resman_ctx->resource_dic)
+            av_dict_free(&resman_ctx->resource_dic);
+        av_freep(&resman_ctx);
+    }
+
+    ff_mutex_unlock(&mutex);
+}
+
+
+char *ff_resman_get_string(FFResourceId resource_id)
+{
+    ResourceManagerContext *ctx               = get_resman_context();
+    FFResourceDefinition resource_definition = { 0 };
+    AVDictionaryEntry *dic_entry;
+    char *res = NULL;
+
+    if (!ctx)
+        return NULL;
+
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(resource_definitions); ++i) {
+        FFResourceDefinition def = resource_definitions[i];
+        if (def.resource_id == resource_id) {
+            resource_definition = def;
+            break;
+        }
+    }
+
+    if (!resource_definition.name) {
+        av_log(ctx, AV_LOG_ERROR, "Unable to find resource with ID %d\n", resource_id);
+        return NULL;
+    }
+
+    ff_mutex_lock(&mutex);
+
+    dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+    if (!dic_entry) {
+        char *out = NULL;
+        size_t out_len;
+        int dict_ret;
+
+        int ret = decompress_gzip(ctx, (uint8_t *)resource_definition.data, *resource_definition.data_len, &out, &out_len);
+
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to decompress the resource with ID %d\n", resource_id);
+            goto end;
+        }
+
+        dict_ret = av_dict_set(&ctx->resource_dic, resource_definition.name, out, 0);
+        if (dict_ret < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to store decompressed resource in dictionary: %d\n", dict_ret);
+            av_freep(&out);
+            goto end;
+        }
+
+        av_freep(&out);
+        dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+        if (!dic_entry) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to retrieve resource from dictionary after storing it\n");
+            goto end;
+        }
+    }
+
+    res = dic_entry->value;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
diff --git a/fftools/resources/resman.h b/fftools/resources/resman.h
new file mode 100644
index 0000000000..6485db5091
--- /dev/null
+++ b/fftools/resources/resman.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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_RESOURCES_RESMAN_H
+#define FFTOOLS_RESOURCES_RESMAN_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "fftools/ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+#include "fftools/textformat/avtextformat.h"
+
+typedef enum {
+    FF_RESOURCE_GRAPH_CSS,
+    FF_RESOURCE_GRAPH_HTML,
+} FFResourceId;
+
+typedef struct FFResourceDefinition {
+    FFResourceId resource_id;
+    const char *name;
+
+    const unsigned char *data;
+    const unsigned *data_len;
+
+} FFResourceDefinition;
+
+void ff_resman_uninit(void);
+
+char *ff_resman_get_string(FFResourceId resource_id);
+
+#endif /* FFTOOLS_RESOURCES_RESMAN_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 09/11] fftools/ffmpeg_mux: Make ms_from_ost() inline
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
                       ` (7 preceding siblings ...)
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 08/11] fftools/resources: Add resource manager files softworkz
@ 2025-04-18  2:57     ` softworkz
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 10/11] fftools/graphprint: Add execution graph printing softworkz
                       ` (2 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:57 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_mux.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fftools/ffmpeg_mux.h b/fftools/ffmpeg_mux.h
index f41f2c18fa..4ca8ab73a4 100644
--- a/fftools/ffmpeg_mux.h
+++ b/fftools/ffmpeg_mux.h
@@ -123,7 +123,7 @@ typedef struct Muxer {
 
 int mux_check_init(void *arg);
 
-static MuxStream *ms_from_ost(OutputStream *ost)
+static inline MuxStream *ms_from_ost(OutputStream *ost)
 {
     return (MuxStream*)ost;
 }
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 10/11] fftools/graphprint: Add execution graph printing
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
                       ` (8 preceding siblings ...)
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 09/11] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
@ 2025-04-18  2:57     ` softworkz
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 11/11] fftools/graphprint: Now, make it a Killer-Feature! softworkz
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-18  2:57 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

The key benefits are:

- Different to other graph printing methods, this is outputting:
  - all graphs with runtime state
    (including auto-inserted filters)
  - each graph with its inputs and outputs
  - all filters with their in- and output pads
  - all connections between all input- and output pads
  - for each connection:
    - the runtime-negotiated format and media type
    - the hw context
    - if video hw context, both: hw pixfmt + sw pixfmt
- Output can either be printed to stdout or written to specified file
- Output is machine-readable
- Use the same output implementation as ffprobe, supporting multiple
  formats

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi                   |   10 +
 fftools/Makefile                  |   20 +-
 fftools/ffmpeg.c                  |    4 +
 fftools/ffmpeg.h                  |    3 +
 fftools/ffmpeg_filter.c           |    5 +
 fftools/ffmpeg_opt.c              |   13 +
 fftools/graph/graphprint.c        | 1102 +++++++++++++++++++++++++++++
 fftools/graph/graphprint.h        |   30 +
 fftools/textformat/avtextformat.c |    2 +
 fftools/textformat/avtextformat.h |   29 +
 fftools/textformat/tf_mermaid.c   |  658 +++++++++++++++++
 fftools/textformat/tf_mermaid.h   |   41 ++
 12 files changed, 1916 insertions(+), 1 deletion(-)
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 17ba876ea3..35675b5309 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1394,6 +1394,16 @@ It is on by default, to explicitly disable it you need to specify @code{-nostats
 @item -stats_period @var{time} (@emph{global})
 Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
 
+@item -print_graphs (@emph{global})
+Prints execution graph details to stderr in the format set via -print_graphs_format.
+
+@item -print_graphs_file @var{filename} (@emph{global})
+Writes execution graph details to the specified file in the format set via -print_graphs_format.
+
+@item -print_graphs_format @var{format} (@emph{global})
+Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
+The default format is json.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index a30bec889e..361a4fd574 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -9,6 +9,8 @@ AVBASENAMES  = ffmpeg ffplay ffprobe
 ALLAVPROGS   = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
 ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
 
+include $(SRC_PATH)/fftools/resources/Makefile
+
 OBJS-ffmpeg +=                  \
     fftools/ffmpeg_dec.o        \
     fftools/ffmpeg_demux.o      \
@@ -19,8 +21,21 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
+    fftools/graph/graphprint.o        \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
+    fftools/textformat/avtextformat.o \
+    fftools/textformat/tf_compact.o   \
+    fftools/textformat/tf_default.o   \
+    fftools/textformat/tf_flat.o      \
+    fftools/textformat/tf_ini.o       \
+    fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
+    fftools/textformat/tf_xml.o       \
+    fftools/textformat/tw_avio.o      \
+    fftools/textformat/tw_buffer.o    \
+    fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffprobe +=                       \
     fftools/textformat/avtextformat.o \
@@ -29,10 +44,12 @@ OBJS-ffprobe +=                       \
     fftools/textformat/tf_flat.o      \
     fftools/textformat/tf_ini.o       \
     fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
     fftools/textformat/tf_xml.o       \
     fftools/textformat/tw_avio.o      \
     fftools/textformat/tw_buffer.o    \
     fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffplay += fftools/ffplay_renderer.o
 
@@ -42,7 +59,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources fftools/graph
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -57,6 +74,7 @@ fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
 OUTDIRS += fftools/resources
+OUTDIRS += fftools/graph
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..6766ec209c 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -81,6 +81,7 @@
 #include "ffmpeg.h"
 #include "ffmpeg_sched.h"
 #include "ffmpeg_utils.h"
+#include "graph/graphprint.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
+    if (print_graphs || print_graphs_file)
+        print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
+
     if (do_benchmark) {
         int64_t maxrss = getmaxrss() / 1024;
         av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 5869979214..7fbf0ad532 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -717,6 +717,9 @@ extern float max_error_rate;
 extern char *filter_nbthreads;
 extern int filter_complex_nbthreads;
 extern int vstats_version;
+extern int print_graphs;
+extern char *print_graphs_file;
+extern char *print_graphs_format;
 extern int auto_conversion_filters;
 
 extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index eab9487f97..b774606562 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -22,6 +22,7 @@
 
 #include "ffmpeg.h"
 #include "ffmpeg_filter.h"
+#include "graph/graphprint.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -2983,6 +2984,10 @@ read_frames:
     }
 
 finish:
+
+    if (print_graphs || print_graphs_file)
+        print_filtergraph(fg, fgt.graph);
+
     // EOF is normal termination
     if (ret == AVERROR_EOF)
         ret = 0;
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 6ec325f51e..3d1efe32f9 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -47,6 +47,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/stereo3d.h"
+#include "graph/graphprint.h"
 
 HWDevice *filter_hw_device;
 
@@ -75,6 +76,9 @@ float max_error_rate  = 2.0/3;
 char *filter_nbthreads;
 int filter_complex_nbthreads = 0;
 int vstats_version = 2;
+int print_graphs = 0;
+char *print_graphs_file = NULL;
+char *print_graphs_format = NULL;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1735,6 +1739,15 @@ const OptionDef options[] = {
         { .func_arg = opt_filter_complex_script },
         "deprecated, use -/filter_complex instead", "filename" },
 #endif
+    { "print_graphs",   OPT_TYPE_BOOL, 0,
+        { &print_graphs },
+        "print execution graph data to stderr" },
+    { "print_graphs_file", OPT_TYPE_STRING, 0,
+        { &print_graphs_file },
+        "write execution graph data to the specified file", "filename" },
+    { "print_graphs_format", OPT_TYPE_STRING, 0,
+        { &print_graphs_format },
+      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
new file mode 100644
index 0000000000..89c38d2e36
--- /dev/null
+++ b/fftools/graph/graphprint.c
@@ -0,0 +1,1102 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include <string.h>
+#include <stdatomic.h>
+
+#include "graphprint.h"
+
+#include <libavformat/url.h>
+
+#include "fftools/ffmpeg_filter.h"
+#include "fftools/ffmpeg_mux.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+#include "fftools/textformat/avtextformat.h"
+#include "fftools/textformat/tf_mermaid.h"
+#include "fftools/resources/resman.h"
+
+typedef enum {
+    SECTION_ID_ROOT,
+    SECTION_ID_FILTERGRAPHS,
+    SECTION_ID_FILTERGRAPH,
+    SECTION_ID_GRAPH_INPUTS,
+    SECTION_ID_GRAPH_INPUT,
+    SECTION_ID_GRAPH_OUTPUTS,
+    SECTION_ID_GRAPH_OUTPUT,
+    SECTION_ID_FILTERS,
+    SECTION_ID_FILTER,
+    SECTION_ID_FILTER_INPUTS,
+    SECTION_ID_FILTER_INPUT,
+    SECTION_ID_FILTER_OUTPUTS,
+    SECTION_ID_FILTER_OUTPUT,
+    SECTION_ID_HWFRAMESCONTEXT,
+    SECTION_ID_INPUTFILES,
+    SECTION_ID_INPUTFILE,
+    SECTION_ID_INPUTSTREAMS,
+    SECTION_ID_INPUTSTREAM,
+    SECTION_ID_OUTPUTFILES,
+    SECTION_ID_OUTPUTFILE,
+    SECTION_ID_OUTPUTSTREAMS,
+    SECTION_ID_OUTPUTSTREAM,
+    SECTION_ID_STREAMLINKS,
+    SECTION_ID_STREAMLINK,
+    SECTION_ID_DECODERS,
+    SECTION_ID_DECODER,
+    SECTION_ID_ENCODERS,
+    SECTION_ID_ENCODER,
+} SectionID;
+
+static struct AVTextFormatSection sections[] = {
+    [SECTION_ID_ROOT]            = { SECTION_ID_ROOT, "root", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER, { SECTION_ID_FILTERGRAPHS, SECTION_ID_INPUTFILES, SECTION_ID_OUTPUTFILES, SECTION_ID_DECODERS, SECTION_ID_ENCODERS, SECTION_ID_STREAMLINKS, -1 } },
+
+    [SECTION_ID_FILTERGRAPHS]    = { SECTION_ID_FILTERGRAPHS, "graphs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
+    [SECTION_ID_FILTERGRAPH]     = { SECTION_ID_FILTERGRAPH, "graph", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { SECTION_ID_GRAPH_INPUTS, SECTION_ID_GRAPH_OUTPUTS, SECTION_ID_FILTERS, -1 }, .element_name = "graph_info" },
+
+    [SECTION_ID_GRAPH_INPUTS]    = { SECTION_ID_GRAPH_INPUTS, "graph_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_INPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_INPUT]     = { SECTION_ID_GRAPH_INPUT, "graph_input", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_GRAPH_OUTPUTS]   = { SECTION_ID_GRAPH_OUTPUTS, "graph_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_OUTPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_OUTPUT]    = { SECTION_ID_GRAPH_OUTPUT, "graph_output", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTERS]         = { SECTION_ID_FILTERS, "filters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_FILTER, -1 }, .id_key = "graph_id" },
+    [SECTION_ID_FILTER]          = { SECTION_ID_FILTER, "filter", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { SECTION_ID_FILTER_INPUTS, SECTION_ID_FILTER_OUTPUTS, -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_INPUTS]   = { SECTION_ID_FILTER_INPUTS, "filter_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_INPUT, -1 } },
+    [SECTION_ID_FILTER_INPUT]    = { SECTION_ID_FILTER_INPUT, "filter_input", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "source_filter_id", .dest_id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_OUTPUTS]  = { SECTION_ID_FILTER_OUTPUTS, "filter_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_OUTPUT, -1 } },
+    [SECTION_ID_FILTER_OUTPUT]   = { SECTION_ID_FILTER_OUTPUT, "filter_output", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "filter_id", .dest_id_key = "dest_filter_id" },
+
+    [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context",  0, { -1 }, },
+
+    [SECTION_ID_INPUTFILES]      = { SECTION_ID_INPUTFILES, "inputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTFILE]       = { SECTION_ID_INPUTFILE, "inputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_INPUTSTREAMS]    = { SECTION_ID_INPUTSTREAMS, "inputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTSTREAM]     = { SECTION_ID_INPUTSTREAM, "inputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTFILES]     = { SECTION_ID_OUTPUTFILES, "outputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTFILE]      = { SECTION_ID_OUTPUTFILE, "outputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTSTREAMS]   = { SECTION_ID_OUTPUTSTREAMS, "outputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTSTREAM]    = { SECTION_ID_OUTPUTSTREAM, "outputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id", },
+
+    [SECTION_ID_STREAMLINKS]     = { SECTION_ID_STREAMLINKS, "streamlinks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAMLINK, -1 } },
+    [SECTION_ID_STREAMLINK]      = { SECTION_ID_STREAMLINK, "streamlink", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .src_id_key = "source_stream_id", .dest_id_key = "dest_stream_id" },
+
+    [SECTION_ID_DECODERS]        = { SECTION_ID_DECODERS, "decoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_DECODER, -1 } },
+    [SECTION_ID_DECODER]         = { SECTION_ID_DECODER, "decoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "source_id", .dest_id_key = "id" },
+
+    [SECTION_ID_ENCODERS]        = { SECTION_ID_ENCODERS, "encoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_ENCODER, -1 } },
+    [SECTION_ID_ENCODER]         = { SECTION_ID_ENCODER, "encoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "id", .dest_id_key = "dest_id" },
+};
+
+typedef struct GraphPrintContext {
+    AVTextFormatContext *tfc;
+    AVTextWriterContext *wctx;
+    AVDiagramConfig diagram_config;
+
+    int id_prefix_num;
+    int is_diagram;
+    int opt_flags;
+    int skip_buffer_filters;
+    AVBPrint pbuf;
+
+} GraphPrintContext;
+
+/* Text Format API Shortcuts */
+#define print_id(k, v)          print_sanizied_id(gpc, k, v, 0)
+#define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
+#define print_int(k, v)         avtext_print_integer(tfc, k, v)
+#define print_int_opt(k, v)     avtext_print_integer_flags(tfc, k, v, gpc->opt_flags)
+#define print_q(k, v, s)        avtext_print_rational(tfc, k, v, s)
+#define print_str(k, v)         avtext_print_string(tfc, k, v, 0)
+#define print_str_opt(k, v)     avtext_print_string(tfc, k, v, gpc->opt_flags)
+#define print_val(k, v, u)      avtext_print_unit_int(tfc, k, v, u)
+
+#define print_fmt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, 0);    \
+} while (0)
+
+#define print_fmt_opt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, gpc->opt_flags);    \
+} while (0)
+
+
+static atomic_int prefix_num = 0;
+
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    unsigned i;
+    for (i = 0; src[i] && i < dst_size - 1; i++)
+        dst[i]      = (char)av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static char *get_extension(const char *url)
+{
+    const char *ext;
+    URLComponents uc;
+    int ret;
+    char scratchpad[128];
+
+    if (!url)
+        return 0;
+
+    ret = ff_url_decompose(&uc, url, NULL);
+    if (ret < 0)
+        return NULL;
+    for (ext = uc.query; *ext != '.' && ext > uc.path; ext--) {
+    }
+
+    if (*ext != '.')
+        return 0;
+    if (uc.query - ext > sizeof(scratchpad))
+        return NULL; //not enough memory in our scratchpad
+    av_strlcpy(scratchpad, ext + 1, uc.query - ext);
+
+    return av_strdup(scratchpad);
+}
+
+static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+
+    if (!hw_device_context)
+        return;
+
+    print_int_opt("has_hw_device_context", 1);
+    print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
+}
+
+static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    const AVPixFmtDescriptor *pix_desc_hw;
+    const AVPixFmtDescriptor *pix_desc_sw;
+
+    if (!hw_frames_context || !hw_frames_context->device_ctx)
+        return;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT);
+
+    print_int_opt("has_hw_frames_context", 1);
+    print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
+
+    pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
+    if (pix_desc_hw) {
+        print_str("hw_pixel_format", pix_desc_hw->name);
+        if (pix_desc_hw->alias)
+            print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
+    }
+
+    pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
+    if (pix_desc_sw) {
+        print_str("sw_pixel_format", pix_desc_sw->name);
+        if (pix_desc_sw->alias)
+            print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
+    }
+
+    print_int_opt("width", hw_frames_context->width);
+    print_int_opt("height", hw_frames_context->height);
+    print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBufferRef *hw_frames_ctx;
+    char layout_string[64];
+
+    if (!link)
+        return;
+
+    hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
+
+    print_str_opt("media_type", av_get_media_type_string(link->type));
+
+    switch (link->type) {
+    case AVMEDIA_TYPE_VIDEO:
+
+        if (hw_frames_ctx && hw_frames_ctx->data) {
+            AVHWFramesContext *      hwfctx      = (AVHWFramesContext *)hw_frames_ctx->data;
+            const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
+            const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
+            if (pix_desc_hw && pix_desc_sw)
+                print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
+        } else {
+            print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
+        }
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        print_q("sar", link->sample_aspect_ratio, ':');
+
+        if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
+            print_str_opt("color_range", av_color_range_name(link->color_range));
+
+        if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
+            print_str("color_space", av_color_space_name(link->colorspace));
+        break;
+
+    case AVMEDIA_TYPE_SUBTITLE:
+        ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        break;
+
+    case AVMEDIA_TYPE_AUDIO:
+        av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
+        print_str("channel_layout", layout_string);
+        print_val("channels", link->ch_layout.nb_channels, "ch");
+        if (tfc->show_value_unit)
+            print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
+        else
+            print_val("sample_rate", link->sample_rate, "Hz");
+
+        break;
+    }
+
+    print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
+
+    if (hw_frames_ctx && hw_frames_ctx->data)
+        print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
+}
+
+static char sanitize_char(const char c)
+{
+    if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+        return c;
+    return '_';
+}
+
+static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBPrint buf;
+
+    if (!key || !id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    print_str(key, buf.str);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+    AVBPrint buf;
+
+    if (!id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    sec_ctx.context_id = buf.str;
+
+    avtext_print_section_header(tfc, &sec_ctx, section_id);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static const char *get_filterpad_name(const AVFilterPad *pad)
+{
+    return pad ? avfilter_pad_get_name(pad, 0) : "pad";
+}
+
+static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
+
+    ////print_id("filter_id", filter->name);
+
+    if (filter->filter) {
+        print_str("filter_name", filter->filter->name);
+        print_str_opt("description", filter->filter->description);
+        print_int_opt("nb_inputs", filter->nb_inputs);
+        print_int_opt("nb_outputs", filter->nb_outputs);
+    }
+
+    if (filter->hw_device_ctx) {
+        AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
+        print_hwdevicecontext(gpc, device_context);
+        if (filter->extra_hw_frames > 0)
+            print_int("extra_hw_frames", filter->extra_hw_frames);
+    }
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
+
+    for (unsigned i = 0; i < filter->nb_inputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->inputs[i];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
+        sec_ctx.context_type = NULL;
+
+        print_int_opt("input_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
+
+        dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
+        if (dic_entry) {
+            char buf[256];
+            (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
+            print_id_noprefix("source_filter_id", buf);
+        } else {
+            print_id("source_filter_id", link->src->name);
+        }
+
+        print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
+
+    for (unsigned i = 0; i < filter->nb_outputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->outputs[i];
+        char buf[256];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
+        sec_ctx.context_type = NULL;
+
+        dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
+        if (dic_entry) {
+            (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
+            print_id_noprefix("dest_filter_id", buf);
+        } else {
+            print_id("dest_filter_id", link->dst->name);
+        }
+
+        print_int_opt("output_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->srcpad));
+        ////print_id("dest_filter_id", link->dst->name);
+        print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER
+}
+
+static void init_sections(void)
+{
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
+        sections[i].show_all_entries = 1;
+}
+
+static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVDictionary *input_map = NULL;
+    AVDictionary *output_map = NULL;
+
+    print_int("graph_index", fg->index);
+    print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+    print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
+    print_str("description", fgp->graph_desc);
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
+
+    for (int i = 0; i < fg->nb_inputs; i++) {
+        InputFilterPriv *ifilter = ifp_from_ifilter(fg->inputs[i]);
+        enum AVMediaType media_type = ifilter->type;
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
+
+        print_int("input_index", ifilter->index);
+
+        if (ifilter->linklabel)
+            print_str("link_label", (const char*)ifilter->linklabel);
+
+        if (ifilter->filter) {
+            print_id("filter_id", ifilter->filter->name);
+            print_str("filter_name", ifilter->filter->filter->name);
+        }
+
+        if (ifilter->linklabel && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
+        else if (ifilter->opts.name && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->opts.name, 0);
+
+        print_str("media_type", av_get_media_type_string(media_type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
+
+    for (int i = 0; i < fg->nb_outputs; i++) {
+        OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
+
+        print_int("output_index", ofilter->index);
+
+        print_str("name", ofilter->name);
+
+        if (fg->outputs[i]->linklabel)
+            print_str("link_label", (const char*)fg->outputs[i]->linklabel);
+
+        if (ofilter->filter) {
+            print_id("filter_id", ofilter->filter->name);
+            print_str("filter_name", ofilter->filter->filter->name);
+        }
+
+        if (ofilter->name && ofilter->filter)
+            av_dict_set(&output_map, ofilter->filter->name, ofilter->name, 0);
+
+
+        print_str("media_type", av_get_media_type_string(fg->outputs[i]->type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
+
+    if (graph) {
+        AVTextFormatSectionContext sec_ctx = { 0 };
+
+        sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTERS);
+
+        if (gpc->is_diagram) {
+            print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+            print_str("description", fgp->graph_desc);
+            print_str("id", sec_ctx.context_id);
+        }
+
+        av_freep(&sec_ctx.context_id);
+
+        for (unsigned i = 0; i < graph->nb_filters; i++) {
+            AVFilterContext *filter = graph->filters[i];
+
+            if (gpc->skip_buffer_filters) {
+                if (av_dict_get(input_map, filter->name, NULL, 0))
+                    continue;
+                if (av_dict_get(output_map, filter->name, NULL, 0))
+                    continue;
+            }
+
+            sec_ctx.context_id = filter->name;
+
+            print_filter(gpc, filter, input_map, output_map);
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
+    }
+
+    // Clean up dictionaries
+    av_dict_free(&input_map);
+    av_dict_free(&output_map);
+}
+
+static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    AVTextFormatContext       *tfc = gpc->tfc;
+    AVBPrint                   buf;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    sec_ctx.context_id = "Inputs";
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+    print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
+
+    for (int n = nb_ifiles - 1; n >= 0; n--) {
+        InputFile *ifi = ifiles[n];
+        AVFormatContext *fc = ifi->ctx;
+
+        sec_ctx.context_id = av_asprintf("Input_%d", n);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
+        av_freep(&sec_ctx.context_id);
+
+        print_fmt("index", "%d", ifi->index);
+
+        if (fc) {
+            print_str("demuxer_name", fc->iformat->name);
+            if (fc->url) {
+                char *extension = get_extension(fc->url);
+                if (extension) {
+                    print_str("file_extension", extension);
+                    av_freep(&extension);
+                }
+                print_str("url", fc->url);
+            }
+        }
+
+        sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
+
+        av_freep(&sec_ctx.context_id);
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+            const AVCodecDescriptor *codec_desc;
+
+            if (!ist || !ist->par)
+                continue;
+
+            codec_desc = avcodec_descriptor_get(ist->par->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
+
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_in_%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else if (ist->dec) {
+                char char_buf[256];
+                av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
+                av_bprintf(&buf, "%s", "Attachment");
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
+                av_bprintf(&buf, "%s", "Data");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ist->index);
+
+            if (ist->dec)
+                print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
+
+
+    print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
+
+    for (int n = 0; n < nb_ifiles; n++) {
+        InputFile *ifi = ifiles[n];
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+
+            if (!ist->decoder)
+                continue;
+
+            sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_DECODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("source_id", "r_in_%d_%d", n, i);
+            print_fmt("id", "in_%d_%d", n, i);
+
+            ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            print_fmt("name", "%s", ist->dec->name);
+
+            print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_DECODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            ////const AVCodecDescriptor *codec_desc;
+
+            if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
+                continue;
+
+            ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_ENCODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "out__%d_%d", n, i);
+            print_fmt("dest_id", "r_out__%d_%d", n, i);
+
+            print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
+
+    for (int n = nb_ofiles - 1; n >= 0; n--) {
+        OutputFile *of = ofiles[n];
+        Muxer *muxer = (Muxer *)of;
+
+        if (!muxer->fc)
+            continue;
+
+        sec_ctx.context_id = av_asprintf("Output_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
+
+        av_freep(&sec_ctx.context_id);
+
+        ////print_str_opt("index", av_get_media_type_string(of->index));
+        print_fmt("index", "%d", of->index);
+        ////print_str("url", of->url);
+        print_str("muxer_name", muxer->fc->oformat->name);
+        if (of->url) {
+            char *extension = get_extension(of->url);
+            if (extension) {
+                print_str("file_extension", extension);
+                av_freep(&extension);
+            }
+            print_str("url", of->url);
+        }
+
+        sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_out__%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else {
+                av_bprintf(&buf, "%s", "unknown");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ost->index);
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
+
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->ist && !ost->filter) {
+                sec_ctx.context_type = av_get_media_type_string(ost->type);
+                avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_STREAMLINK);
+                sec_ctx.context_type = NULL;
+
+                if (ost->enc) {
+                    print_fmt("dest_stream_id", "out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Transcode");
+                } else {
+                    print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Stream Copy");
+                }
+
+                print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+                avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
+
+    return 0;
+}
+
+
+static void uninit_graphprint(GraphPrintContext *gpc)
+{
+    if (gpc->tfc)
+        avtext_context_close(&gpc->tfc);
+
+    if (gpc->wctx)
+        avtextwriter_context_close(&gpc->wctx);
+
+    // Finalize the print buffer if it was initialized
+    av_bprint_finalize(&gpc->pbuf, NULL);
+}
+
+static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
+{
+    const AVTextFormatter *text_formatter;
+    AVTextFormatContext *tfc = NULL;
+    AVTextWriterContext *wctx = NULL;
+    GraphPrintContext *gpc = NULL;
+    char *w_args = NULL;
+    char *w_name;
+    int ret;
+
+    init_sections();
+    *pgpc = NULL;
+
+    av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("json");
+    if (!print_graphs_format) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &w_args);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    text_formatter = avtext_get_formatter_by_name(w_name);
+    if (!text_formatter) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtextwriter_create_buffer(&wctx, target_buf);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), 0, 0, 0, 0, -1, NULL);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    gpc = av_mallocz(sizeof(GraphPrintContext));
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    gpc->wctx = wctx;
+    gpc->tfc = tfc;
+    av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
+    gpc->is_diagram = !!(tfc->formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
+    if (gpc->is_diagram) {
+        tfc->show_value_unit = 1;
+        tfc->show_optional_fields = -1;
+        gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+        gpc->skip_buffer_filters = 1;
+        ////} else {
+        ////    gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+    }
+
+    if (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml")) {
+        gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
+
+        if (!strcmp(text_formatter->name, "mermaidhtml"))
+            gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
+
+        av_diagram_init(tfc, &gpc->diagram_config);
+    }
+
+    *pgpc = gpc;
+
+    return 0;
+
+fail:
+    if (tfc)
+        avtext_context_close(&tfc);
+    if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
+        avtextwriter_context_close(&wctx);
+    av_freep(&gpc);
+
+    return ret;
+}
+
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVBPrint *target_buf = &fgp->graph_print_buf;
+    int ret;
+
+    if (!fg || !fgp) {
+        av_log(NULL, AV_LOG_ERROR, "Invalid filter graph provided\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (target_buf->len)
+        av_bprint_finalize(target_buf, NULL);
+
+    ret = init_graphprint(&gpc, target_buf);
+    if (ret)
+        return ret;
+
+    if (!gpc) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to initialize graph print context\n");
+        return AVERROR(ENOMEM);
+    }
+
+    tfc = gpc->tfc;
+
+    // Due to the threading model each graph needs to print itself into a buffer
+    // from its own thread. The actual printing happens short before cleanup in ffmpeg.c
+    // where all graphs are assembled together. To make this work, we need to put the
+    // formatting context into the same state like it would be when printing all at once,
+    // so here we print the section headers and clear the buffer to get into the right state.
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+
+    av_bprint_clear(target_buf);
+
+    print_filtergraph_single(gpc, fg, graph);
+
+    if (gpc->is_diagram) {
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+    }
+
+    uninit_graphprint(gpc);
+
+    return 0;
+}
+
+static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    AVBPrint target_buf;
+    int ret;
+
+    ret = init_graphprint(&gpc, &target_buf);
+    if (ret)
+        goto cleanup;
+
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto cleanup;
+    }
+
+    tfc = gpc->tfc;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+
+    for (int i = 0; i < nb_graphs; i++) {
+        FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
+        AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+        if (graph_buf->len > 0) {
+            avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+            av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+            av_bprint_finalize(graph_buf, NULL);
+            avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        }
+    }
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->fg_simple) {
+                FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
+                AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+                if (graph_buf->len > 0) {
+                    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+                    av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+                    av_bprint_finalize(graph_buf, NULL);
+                    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+                }
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+
+    print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ROOT
+
+    if (print_graphs_file) {
+        AVIOContext *avio = NULL;
+
+        if (!strcmp(print_graphs_file, "-")) {
+            printf("%s", target_buf.str);
+        } else {
+            ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
+                goto cleanup;
+            }
+
+            avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
+            avio_flush(avio);
+
+            if ((ret = avio_closep(&avio)) < 0)
+                av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
+        }
+    }
+
+    if (print_graphs)
+        av_log(NULL, AV_LOG_INFO, "%s    %c", target_buf.str, '\n');
+
+cleanup:
+    // Properly clean up resources
+    if (gpc)
+        uninit_graphprint(gpc);
+
+    // Ensure the target buffer is properly finalized
+    av_bprint_finalize(&target_buf, NULL);
+
+    return ret;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+}
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
new file mode 100644
index 0000000000..9f043cc273
--- /dev/null
+++ b/fftools/graph/graphprint.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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_GRAPH_GRAPHPRINT_H
+#define FFTOOLS_GRAPH_GRAPHPRINT_H
+
+#include "fftools/ffmpeg.h"
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles);
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index d2d84c4f1d..0692dbc59d 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -709,6 +709,8 @@ static void formatters_register_all(void)
     registered_formatters[4] = &avtextformatter_ini;
     registered_formatters[5] = &avtextformatter_json;
     registered_formatters[6] = &avtextformatter_xml;
+    registered_formatters[7] = &avtextformatter_mermaid;
+    registered_formatters[8] = &avtextformatter_mermaidhtml;
 }
 
 const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index 16cd9b214f..4890cb846d 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -31,6 +31,12 @@
 
 #define SECTION_MAX_NB_CHILDREN 11
 
+typedef struct AVTextFormatSectionContext {
+    char *context_id;
+    const char *context_type;
+    int context_flags;
+} AVTextFormatSectionContext;
+
 
 typedef struct AVTextFormatSection {
     int id;             ///< unique id identifying a section
@@ -42,6 +48,10 @@ typedef struct AVTextFormatSection {
                                            ///  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
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE       32 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS      64 ///< ...
+#define AV_TEXTFORMAT_SECTION_PRINT_TAGS         128 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH   256 ///< ...
 
     int flags;
     const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
@@ -50,12 +60,17 @@ typedef struct AVTextFormatSection {
     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;
+    const char *id_key;          ///< name of the key to be used as the id 
+    const char *src_id_key;     ///< name of the key to be used as the source id for diagram connections
+    const char *dest_id_key;   ///< name of the key to be used as the target id for diagram connections
+    const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
 } AVTextFormatSection;
 
 typedef struct AVTextFormatContext AVTextFormatContext;
 
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
+#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER         4
 
 typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
@@ -64,6 +79,18 @@ typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_NB
 } StringValidation;
 
+typedef enum {
+    AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_NONDIR,
+    AV_TEXTFORMAT_LINKTYPE_HIDDEN,
+    AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
+} AVTextFormatLinkType;
+
 typedef struct AVTextFormatter {
     const AVClass *priv_class;      ///< private class of the formatter, if any
     int priv_size;                  ///< private size for the formatter context
@@ -167,5 +194,7 @@ extern const AVTextFormatter avtextformatter_flat;
 extern const AVTextFormatter avtextformatter_ini;
 extern const AVTextFormatter avtextformatter_json;
 extern const AVTextFormatter avtextformatter_xml;
+extern const AVTextFormatter avtextformatter_mermaid;
+extern const AVTextFormatter avtextformatter_mermaidhtml;
 
 #endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
diff --git a/fftools/textformat/tf_mermaid.c b/fftools/textformat/tf_mermaid.c
new file mode 100644
index 0000000000..6147cf6eea
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.c
@@ -0,0 +1,658 @@
+/*
+ * 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 "tf_internal.h"
+#include "tf_mermaid.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+
+static const char *init_directive = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 10,"
+        "\"nodeSpacing\": 10,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"flowchart\": { "
+            "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
+            "\"diagramPadding\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char* init_directive_er = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"layout\": \"elk\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 65,"
+        "\"nodeSpacing\": 60,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"er\": { "
+            "\"diagramPadding\": 12, "
+            "\"entityPadding\": 4, "
+            "\"minEntityWidth\": 150, "
+            "\"minEntityHeight\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char *theme_css_er = ""
+
+    // Variables
+            ".root { "
+                "--ff-colvideo: #6eaa7b; "
+                "--ff-colaudio: #477fb3; "
+                "--ff-colsubtitle: #ad76ab; "
+                "--ff-coltext: #666; "
+            "} "
+            " g.nodes g.node.default rect.basic.label-container, "
+            " g.nodes g.node.default path { "
+            "     rx: 1; "
+            "     ry: 1; "
+            "     stroke-width: 1px !important; "
+            "     stroke: #e9e9e9 !important; "
+            "     fill: url(#ff-filtergradient) !important; "
+            "     filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
+            "     fill: white !important; "
+            " } "
+            "  "
+            " .relationshipLine { "
+            "     stroke: gray; "
+            "     stroke-width: 1; "
+            "     fill: none; "
+            "     filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
+            " } "
+            "  "
+            " g.node.default g.label.name  foreignObject > div > span > p, "
+            " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
+            "     font-size: 0.95rem; "
+            "     font-weight: 500; "
+            "     text-transform: uppercase; "
+            "     min-width: 5.5rem; "
+            "     margin-bottom: 0.5rem; "
+            "      "
+            " } "
+            "  "
+            " .edgePaths path { "
+            "     marker-end: none; "
+            "     marker-start: none; "
+            "  "
+            "} ";
+
+
+/* Mermaid Graph output */
+
+typedef struct MermaidContext {
+    const AVClass *class;
+    AVDiagramConfig *diagram_config;
+    int subgraph_count;
+    int within_tag;
+    int indent_level;
+    int create_html;
+
+    // Options
+    int enable_link_colors; // Requires Mermaid 11.5
+
+    struct section_data {
+        const char *section_id;
+        const char *section_type;
+        const char *src_id;
+        const char *dest_id;
+        AVTextFormatLinkType link_type;
+        int current_is_textblock;
+        int current_is_stadium;
+        int subgraph_start_incomplete;
+    }  section_data[SECTION_MAX_NB_LEVELS];
+
+    unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint link_buf; ///< print buffer for writing diagram links
+    AVDictionary *link_dict;
+} MermaidContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(MermaidContext, x)
+
+static const AVOption mermaid_options[] = {
+    { "link_coloring",    "enable colored links (requires Mermaid >= 11.5)",  OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL,   { .i64 = 1 },  0, 1 },
+    ////{"diagram_css",      "CSS for the diagram",                              OFFSET(diagram_css),        AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    ////{"html_template",    "Template HTML",                                    OFFSET(html_template),      AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    { NULL },
+};
+
+DEFINE_FORMATTER_CLASS(mermaid);
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
+{
+    MermaidContext *mmc = tfc->priv;
+    mmc->diagram_config = diagram_config;
+}
+
+static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
+{
+    MermaidContext *mmc = tfc->priv;
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&buf, "%s--%s", src, dest);
+
+    if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
+        return 1;
+
+    av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
+
+    return 0;
+}
+
+static av_cold int mermaid_init(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
+    return 0;
+}
+
+static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    int ret = mermaid_init(tfc);
+
+    if (ret < 0)
+        return ret;
+
+    mmc->create_html = 1;
+
+    return 0;
+}
+
+#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
+
+static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
+{
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSectionContext *sec_ctx = data;
+
+    if (tfc->level == 0) {
+        char *directive;
+        AVBPrint css_buf;
+        const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
+        char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
+        (void)theme_css_er;
+        ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
+        av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+        av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
+        av_freep(&single_line_css);
+
+        directive = av_strireplace(diag_directive, "__###__", css_buf.str);
+        if (mmc->create_html) {
+            uint64_t length;
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+
+            length = token_pos - mmc->diagram_config->html_template;
+            for (uint64_t i = 0; i < length; i++)
+                writer_w8(tfc, mmc->diagram_config->html_template[i]);
+        }
+
+        writer_put_str(tfc, directive);
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+            writer_put_str(tfc, "flowchart LR\n");
+        ////writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
+            writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%);     \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%);   \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%);     \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            writer_put_str(tfc, "erDiagram\n");
+            break;
+        }
+
+        return;
+    }
+
+    if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
+        AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
+
+        if (parent_sec_data.subgraph_start_incomplete) {
+
+            if (parent_buf->len > 0)
+                writer_printf(tfc, "%s", parent_buf->str);
+
+            writer_put_str(tfc, "</div>\"]\n");
+
+            mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
+        }
+    }
+
+    av_freep(&mmc->section_data[tfc->level].section_id);
+    av_freep(&mmc->section_data[tfc->level].section_type);
+    av_freep(&mmc->section_data[tfc->level].src_id);
+    av_freep(&mmc->section_data[tfc->level].dest_id);
+    mmc->section_data[tfc->level].current_is_textblock = 0;
+    mmc->section_data[tfc->level].current_is_stadium = 0;
+    mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
+    mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
+
+    // NOTE: av_strdup() allocations aren't checked
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+            MM_INDENT();
+            writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+
+            mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+                if (sec_ctx->context_flags & 1) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_textblock = 1;
+                } else if (sec_ctx->context_flags & 2) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s([\"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_stadium = 1;
+                } else {
+                    MM_INDENT();
+                    writer_printf(tfc, "%s(\"", sec_ctx->context_id);
+                }
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+                MM_INDENT();
+                writer_printf(tfc, "%s {\n", sec_ctx->context_id);
+                break;
+            }
+
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
+
+        if (sec_ctx && sec_ctx->context_type)
+            writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
+        else
+            writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        av_bprint_clear(buf);
+        mmc->nb_link_captions[tfc->level] = 0;
+
+        if (sec_ctx && sec_ctx->context_type)
+            mmc->section_data[tfc->level].section_type = av_strdup(sec_ctx->context_type);
+
+        ////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(tfc, " type=\"%s\"", buf.str);
+    }
+}
+
+static void mermaid_print_section_footer(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
+        writer_put_str(tfc, "</div>");
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (sec_data.current_is_textblock) {
+                writer_printf(tfc, "\"}\n", section->name);
+
+                if (sec_data.section_id) {
+                    MM_INDENT();
+                    writer_put_str(tfc, "class ");
+                    writer_put_str(tfc, sec_data.section_id);
+                    writer_put_str(tfc, " ff-");
+                    writer_put_str(tfc, section->name);
+                    writer_put_str(tfc, "\n");
+                }
+            } else if (sec_data.current_is_stadium) {
+                writer_printf(tfc, "\"]):::ff-%s\n", section->name);
+            } else {
+                writer_printf(tfc, "\"):::ff-%s\n", section->name);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            MM_INDENT();
+            writer_put_str(tfc, "}\n\n");
+            break;
+        }
+
+        mmc->indent_level--;
+
+    } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
+
+        MM_INDENT();
+        writer_put_str(tfc, "end\n");
+
+        if (sec_data.section_id) {
+            MM_INDENT();
+            writer_put_str(tfc, "class ");
+            writer_put_str(tfc, sec_data.section_id);
+            writer_put_str(tfc, " ff-");
+            writer_put_str(tfc, section->name);
+            writer_put_str(tfc, "\n");
+        }
+
+        mmc->indent_level--;
+    }
+
+    if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
+        if (sec_data.src_id && sec_data.dest_id
+            && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+
+                if (sec_data.section_type && mmc->enable_link_colors)
+                    av_bprintf(&mmc->link_buf, "\n  %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
+                else
+                    av_bprintf(&mmc->link_buf, "\n  %s ==", sec_data.src_id);
+
+                if (buf->len > 0) {
+                    av_bprintf(&mmc->link_buf, " \"%s", buf->str);
+
+                    for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
+                        av_bprintf(&mmc->link_buf, "<br>&nbsp;");
+
+                    av_bprintf(&mmc->link_buf, "\" ==");
+                }
+
+                av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+
+                av_bprintf(&mmc->link_buf, "\n  %s", sec_data.src_id);
+
+                switch (sec_data.link_type) {
+                case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
+                    break;
+                default:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                }
+
+                av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
+
+                break;
+            }
+
+    if (tfc->level == 0) {
+
+        writer_put_str(tfc, "\n");
+        if (mmc->create_html) {
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+            token_pos += strlen("__###__");
+            writer_put_str(tfc, token_pos);
+        }
+    }
+
+    if (tfc->level == 1) {
+
+        if (mmc->link_buf.len > 0) {
+            writer_put_str(tfc, mmc->link_buf.str);
+            av_bprint_clear(&mmc->link_buf);
+        }
+
+        writer_put_str(tfc, "\n");
+    }
+}
+
+static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
+                                const char *str, int64_t num, const int is_int)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+    int exit = 0;
+
+    if (section->id_key && !strcmp(section->id_key, key)) {
+        mmc->section_data[tfc->level].section_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
+        mmc->section_data[tfc->level].dest_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->src_id_key && !strcmp(section->src_id_key, key)) {
+        mmc->section_data[tfc->level].src_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->linktype_key && !strcmp(section->linktype_key, key)) {
+        mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
+        exit = 1;
+    }
+
+    //if (exit)
+    //    return;
+
+    if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
+        || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
+
+        if (exit)
+            return;
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (is_int) {
+                writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
+            } else {
+                ////AVBPrint b;
+                ////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
+                const char *tmp = av_strireplace(str, "\"", "'");
+                ////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
+                writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
+                av_freep(&tmp);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+            if (!is_int && str)
+            {
+                const char *col_type;
+
+                if (key[0] == '_')
+                    return;
+
+                if (sec_data.section_id && !strcmp(str, sec_data.section_id))
+                    col_type = "PK";
+                else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
+                    col_type = "FK";
+                else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
+                    col_type = "FK";
+                else
+                    col_type = "";
+
+                MM_INDENT();
+
+                if (is_int)
+                    writer_printf(tfc, "    %s %"PRId64" %s\n", key, num, col_type);
+                else
+                    writer_printf(tfc, "    %s %s %s\n", key, str, col_type);
+            }
+            break;
+        }
+
+    } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        if (exit)
+            return;
+
+        if (buf->len > 0)
+            av_bprintf(buf, "%s", "<br>");
+
+        av_bprintf(buf, "");
+        if (is_int)
+            av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
+        else
+            av_bprintf(buf, "<span>%s</span>", str);
+
+        mmc->nb_link_captions[tfc->level]++;
+    }
+}
+
+static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
+{
+    mermaid_print_value(tfc, key, value, 0, 0);
+}
+
+static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
+{
+    mermaid_print_value(tfc, key, NULL, value, 1);
+}
+
+const AVTextFormatter avtextformatter_mermaid = {
+    .name                 = "mermaid",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
+
+
+const AVTextFormatter avtextformatter_mermaidhtml = {
+    .name                 = "mermaidhtml",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init_html,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
diff --git a/fftools/textformat/tf_mermaid.h b/fftools/textformat/tf_mermaid.h
new file mode 100644
index 0000000000..aff73bf9f3
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.h
@@ -0,0 +1,41 @@
+/*
+ * 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_TF_MERMAID_H
+#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
+
+typedef enum {
+    AV_DIAGRAMTYPE_GRAPH,
+    AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
+} AVDiagramType;
+
+typedef struct AVDiagramConfig {
+    AVDiagramType diagram_type;
+    const char *diagram_css;
+    const char *html_template;
+} AVDiagramConfig;
+
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
+
+void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
+
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */
\ No newline at end of file
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v3 11/11] fftools/graphprint: Now, make it a Killer-Feature!
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
                       ` (9 preceding siblings ...)
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 10/11] fftools/graphprint: Add execution graph printing softworkz
@ 2025-04-18  2:57     ` softworkz
  2025-04-20 10:11       ` Michael Niedermayer
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
  11 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-18  2:57 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

remember this: -sg   <= show-graph

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi              |   4 +
 fftools/Makefile             |   1 +
 fftools/ffmpeg.c             |   2 +-
 fftools/ffmpeg.h             |   1 +
 fftools/ffmpeg_filter.c      |   2 +-
 fftools/ffmpeg_opt.c         |   4 +
 fftools/graph/filelauncher.c | 204 +++++++++++++++++++++++++++++++++++
 fftools/graph/graphprint.c   |  50 ++++++++-
 fftools/graph/graphprint.h   |  32 ++++++
 9 files changed, 295 insertions(+), 5 deletions(-)
 create mode 100644 fftools/graph/filelauncher.c

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 35675b5309..6e9e7aed0e 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1404,6 +1404,10 @@ Writes execution graph details to the specified file in the format set via -prin
 Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
 The default format is json.
 
+@item -sg (@emph{global})
+Writes the execution graph to a temporary html file (mermaidhtml format) and 
+tries to launch it in the default browser.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index 361a4fd574..56a2910212 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -22,6 +22,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
     fftools/graph/graphprint.o        \
+    fftools/graph/filelauncher.o      \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
     fftools/textformat/avtextformat.o \
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index 6766ec209c..9875a1f7fd 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -309,7 +309,7 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
 
     if (do_benchmark) {
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7fbf0ad532..49fea0307d 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -721,6 +721,7 @@ extern int print_graphs;
 extern char *print_graphs_file;
 extern char *print_graphs_format;
 extern int auto_conversion_filters;
+extern int show_graph;
 
 extern const AVIOInterruptCB int_cb;
 
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index b774606562..e82e333b7f 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -2985,7 +2985,7 @@ read_frames:
 
 finish:
 
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraph(fg, fgt.graph);
 
     // EOF is normal termination
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3d1efe32f9..24713d640f 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -79,6 +79,7 @@ int vstats_version = 2;
 int print_graphs = 0;
 char *print_graphs_file = NULL;
 char *print_graphs_format = NULL;
+int show_graph = 0;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1748,6 +1749,9 @@ const OptionDef options[] = {
     { "print_graphs_format", OPT_TYPE_STRING, 0,
         { &print_graphs_format },
       "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
+    { "sg",   OPT_TYPE_BOOL, 0,
+        { &show_graph },
+        "create execution graph as temporary html file and try to launch it in the default browser" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/filelauncher.c b/fftools/graph/filelauncher.c
new file mode 100644
index 0000000000..ae9d88c2e4
--- /dev/null
+++ b/fftools/graph/filelauncher.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(_WIN32)
+#  include <windows.h>
+#else
+#  include <sys/time.h>
+#  include <time.h>
+#endif
+#include "graphprint.h"
+
+int ff_open_html_in_browser(const char *html_path)
+{
+    if (!html_path || !*html_path)
+        return -1;
+
+#if defined(_WIN32)
+
+    // --- Windows ---------------------------------
+    {
+        HINSTANCE rc = ShellExecuteA(NULL, "open", html_path, NULL, NULL, SW_SHOWNORMAL);
+        if ((UINT_PTR)rc <= 32) {
+            // Fallback: system("start ...")
+            char cmd[1024];
+            _snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "start \"\" \"%s\"", html_path);
+            if (system(cmd) != 0)
+                return -1;
+        }
+        return 0;
+    }
+
+#elif defined(__APPLE__)
+
+    // --- macOS -----------------------------------
+    {
+        // "open" is the macOS command to open a file/URL with the default application
+        char cmd[1024];
+        snprintf(cmd, sizeof(cmd), "open '%s' 1>/dev/null 2>&1 &", html_path);
+        if (system(cmd) != 0)
+            return -1;
+        return 0;
+    }
+
+#else
+
+    // --- Linux / Unix-like -----------------------
+    // We'll try xdg-open, then gnome-open, then kfmclient
+    {
+        // Helper macro to try one browser command
+        // Returns 0 on success, -1 on failure
+        #define TRY_CMD(prog) do {                                   \
+            char buf[1024];                                          \
+            snprintf(buf, sizeof(buf), "%s '%s' 1>/dev/null 2>&1 &", \
+                     (prog), html_path);                              \
+            int ret = system(buf);                                    \
+            /* On Unix: system() returns -1 if the shell can't run. */\
+            /* Otherwise, check exit code in lower 8 bits.           */\
+            if (ret != -1 && WIFEXITED(ret) && WEXITSTATUS(ret) == 0) \
+                return 0;                                             \
+        } while (0)
+
+        TRY_CMD("xdg-open");
+        TRY_CMD("gnome-open");
+        TRY_CMD("kfmclient exec");
+
+        fprintf(stderr, "Could not open '%s' in a browser.\n", html_path);
+        return -1;
+    }
+
+#endif
+}
+
+
+int ff_get_temp_dir(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    // --- Windows ------------------------------------
+    {
+        // GetTempPathA returns length of the string (including trailing backslash).
+        // If the return value is greater than buffer size, it's an error.
+        DWORD len = GetTempPathA((DWORD)size, buf);
+        if (len == 0 || len > size) {
+            // Could not retrieve or buffer is too small
+            return -1;
+        }
+        return 0;
+    }
+
+#else
+
+    // --- macOS / Linux / Unix -----------------------
+    // Follow typical POSIX convention: check common env variables
+    // and fallback to /tmp if not found.
+    {
+        const char *tmp = getenv("TMPDIR");
+        if (!tmp || !*tmp) tmp = getenv("TMP");
+        if (!tmp || !*tmp) tmp = getenv("TEMP");
+        if (!tmp || !*tmp) tmp = "/tmp";
+
+        // Copy into buf, ensure there's a trailing slash
+        size_t len = strlen(tmp);
+        if (len + 2 > size) {
+            // Need up to len + 1 for slash + 1 for null terminator
+            return -1;
+        }
+
+        strcpy(buf, tmp);
+        // Append slash if necessary
+        if (buf[len - 1] != '/' && buf[len - 1] != '\\') {
+#if defined(__APPLE__)
+            // On macOS/Unix, use forward slash
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#else
+            // Technically on Unix it's always '/', but here's how you'd do if needed:
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#endif
+        }
+        return 0;
+    }
+
+#endif
+}
+
+int ff_make_timestamped_html_name(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    /*----------- Windows version -----------*/
+    SYSTEMTIME st;
+    GetLocalTime(&st);
+    /*
+      st.wYear, st.wMonth, st.wDay,
+      st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
+    */
+    int written = _snprintf_s(buf, size, _TRUNCATE,
+                              "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                              st.wYear,
+                              st.wMonth,
+                              st.wDay,
+                              st.wHour,
+                              st.wMinute,
+                              st.wSecond,
+                              st.wMilliseconds);
+    if (written < 0)
+        return -1; /* Could not write into buffer */
+    return 0;
+
+#else
+
+    /*----------- macOS / Linux / Unix version -----------*/
+    struct timeval tv;
+    if (gettimeofday(&tv, NULL) != 0) {
+        return -1; /* gettimeofday failed */
+    }
+
+    struct tm local_tm;
+    localtime_r(&tv.tv_sec, &local_tm);
+
+    int ms = (int)(tv.tv_usec / 1000); /* convert microseconds to milliseconds */
+
+    /* 
+       local_tm.tm_year is years since 1900,
+       local_tm.tm_mon  is 0-based (0=Jan, 11=Dec) 
+    */
+    int written = snprintf(buf, size,
+                           "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                           local_tm.tm_year + 1900,
+                           local_tm.tm_mon + 1,
+                           local_tm.tm_mday,
+                           local_tm.tm_hour,
+                           local_tm.tm_min,
+                           local_tm.tm_sec,
+                           ms);
+    if (written < 0 || (size_t)written >= size) {
+        return -1; /* Buffer too small or formatting error */
+    }
+    return 0;
+
+#endif
+}
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
index 89c38d2e36..8241c51e6c 100644
--- a/fftools/graph/graphprint.c
+++ b/fftools/graph/graphprint.c
@@ -586,8 +586,6 @@ static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifil
     AVBPrint                   buf;
     AVTextFormatSectionContext sec_ctx = { 0 };
 
-    sec_ctx.context_id = "Inputs";
-
     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
 
     print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
@@ -875,6 +873,11 @@ static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
 
     av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
 
+    if (show_graph) {
+        if (!print_graphs_format || strcmp(print_graphs_format, "mermaidhtml") != 0)
+            print_graphs_format = av_strdup("mermaidhtml");
+    }
+
     if (!print_graphs_format)
         print_graphs_format = av_strdup("json");
     if (!print_graphs_format) {
@@ -1098,5 +1101,46 @@ cleanup:
 
 int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
 {
-    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+    int ret;
+
+    if (show_graph) {
+        char buf[2048];
+        AVBPrint bp;
+
+        av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+        print_graphs = 0;
+
+        ret = ff_get_temp_dir(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error getting temp directory path for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        ret = ff_make_timestamped_html_name(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error creating temp file name for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        av_bprint_finalize(&bp, &print_graphs_file);
+    }
+
+    ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    if (!ret && show_graph) {
+        av_log(NULL, AV_LOG_INFO, "Execution graph saved as: %s\n", print_graphs_file);
+        av_log(NULL, AV_LOG_INFO, "Trying to launch graph in browser...\n");
+
+        ret = ff_open_html_in_browser(print_graphs_file);
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Browser could not be launched for execution graph display\nPlease open manually: %s\n", print_graphs_file);
+        }
+    }
+
+    return ret;
 }
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
index 9f043cc273..43f769870b 100644
--- a/fftools/graph/graphprint.h
+++ b/fftools/graph/graphprint.h
@@ -27,4 +27,36 @@ int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles,
 
 int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
 
+/**
+ * Open an HTML file in the default browser (Windows, macOS, Linux/Unix).
+ *
+ * @param html_path Absolute or relative path to the HTML file.
+ * @return 0 on success, -1 on failure.
+ *
+ * NOTE: This uses system() calls for non-Windows, and ShellExecute on Windows.
+ *       Exercise caution if 'html_path' is untrusted (possible command injection).
+ */
+int ff_open_html_in_browser(const char *html_path);
+
+/**
+ * Retrieve the system's temporary directory.
+ *
+ * @param buf  Output buffer to store the temp directory path (including trailing slash)
+ * @param size Size of the output buffer in bytes
+ * @return 0 on success, -1 on failure (buffer too small or other errors)
+ *
+ * Note: On most platforms, the path will include a trailing slash (e.g. "C:\\Users\\...\\Temp\\" on Windows, "/tmp/" on Unix).
+ */
+int ff_get_temp_dir(char *buf, size_t size);
+
+/**
+ * Create a timestamped HTML filename, e.g.:
+ *   ffmpeg_graph_2024-01-01_22-12-59_123.html
+ *
+ * @param buf  Pointer to buffer where the result is stored
+ * @param size Size of the buffer in bytes
+ * @return 0 on success, -1 on error (e.g. buffer too small)
+ */
+int ff_make_timestamped_html_name(char *buf, size_t size);
+
 #endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat: Quality improvements
  2025-04-16 11:33       ` softworkz .
  2025-04-18  2:48         ` softworkz .
@ 2025-04-18  5:41         ` softworkz .
  1 sibling, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-18  5:41 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> softworkz .
> Sent: Mittwoch, 16. April 2025 13:33
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat:
> Quality improvements
> 
> 
> 
> > -----Original Message-----
> > From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> > Andreas Rheinhardt
> > Sent: Mittwoch, 16. April 2025 12:49
> > To: ffmpeg-devel@ffmpeg.org
> > Subject: Re: [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat:
> > Quality improvements
> >
> > softworkz:
> > > From: softworkz <softworkz@hotmail.com>
> > >
> > > Signed-off-by: softworkz <softworkz@hotmail.com>
> > > ---
> > >  fftools/textformat/avtextformat.c | 111 +++++++++++++++++++------
> --
> > ---
> > >  fftools/textformat/avtextformat.h |   6 +-
> > >  fftools/textformat/tf_default.c   |   8 ++-
> > >  fftools/textformat/tf_ini.c       |   2 +-
> > >  fftools/textformat/tf_json.c      |   8 ++-
> > >  fftools/textformat/tf_xml.c       |   3 -
> > >  fftools/textformat/tw_avio.c      |   9 ++-
> > >  7 files changed, 93 insertions(+), 54 deletions(-)
> > >
> > > diff --git a/fftools/textformat/avtextformat.c
> > b/fftools/textformat/avtextformat.c
> > > index 811b14b999..4c8def8e65 100644
> > > --- a/fftools/textformat/avtextformat.c
> > > +++ b/fftools/textformat/avtextformat.c
> > > @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
> > >
> > >  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++)
> > > +    for (unsigned i = 0; i < ubuf_size; i++)
> > >          av_bprintf(bp, "%02X", ubuf[i]);
> > >  }
> > >
> > > @@ -141,7 +140,10 @@ int avtext_context_open(AVTextFormatContext
> > **ptctx,
> > >      AVTextFormatContext *tctx;
> > >      int i, ret = 0;
> > >
> > > -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> > > +    if (!ptctx || !formatter)
> > > +        return AVERROR(EINVAL);
> > > +
> > > +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
> > >          ret = AVERROR(ENOMEM);
> > >          goto fail;
> > >      }
> > > @@ -213,25 +215,26 @@ int avtext_context_open(AVTextFormatContext
> > **ptctx,
> > >                      av_log(NULL, AV_LOG_ERROR, " %s", n);
> > >                  av_log(NULL, AV_LOG_ERROR, "\n");
> > >              }
> > > -            return ret;
> > > +            goto fail;
> > >          }
> > >
> > >      /* validate replace string */
> > >      {
> > > -        const uint8_t *p = tctx->string_validation_replacement;
> > > -        const uint8_t *endp = p + strlen(p);
> > > +        const uint8_t *p = (uint8_t *)tctx-
> > >string_validation_replacement;
> > > +        const uint8_t *endp = p + strlen((const char *)p);
> >
> > We use -Wno-pointer-sign in order to avoid these ugly casts.
> 
> Yep, I know. Since I'm not a C-for-life developer, I'm using and
> taking attention of warnings and hints like clang-tidy.
> When you have a file with dozens of warnings these things are not
> helpful, because you cannot go through all of them a hundred times.
> When disabling certain warnings altogether, nothing is won, because
> often there's one in ten cases where the it hints at a problem while
> all others are harmless.
> In order to get rid of a warning, you can either add ugly comments
> or - the designated way from the compiler side is to be explicit by
> e.g. making explicit casts. That's why you see them at some places.
> Doing so, improves quality when working - even for languages that
> I know in a similar way like you know C.
> 
> If we could agree to remove these in a future commit, it would be
> great. 

Hi Andreas,

as my comment above sounds like a typical never-gonna-happen story
I have prepared a commit for later removal of all redundant casts:

https://github.com/softworkz/FFmpeg/tree/submit_textformat_remove_casts

Thanks,
sw






_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v3 11/11] fftools/graphprint: Now, make it a Killer-Feature!
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 11/11] fftools/graphprint: Now, make it a Killer-Feature! softworkz
@ 2025-04-20 10:11       ` Michael Niedermayer
  0 siblings, 0 replies; 130+ messages in thread
From: Michael Niedermayer @ 2025-04-20 10:11 UTC (permalink / raw)
  To: FFmpeg development discussions and patches


[-- Attachment #1.1: Type: text/plain, Size: 2038 bytes --]

On Fri, Apr 18, 2025 at 02:57:04AM +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> remember this: -sg   <= show-graph
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  doc/ffmpeg.texi              |   4 +
>  fftools/Makefile             |   1 +
>  fftools/ffmpeg.c             |   2 +-
>  fftools/ffmpeg.h             |   1 +
>  fftools/ffmpeg_filter.c      |   2 +-
>  fftools/ffmpeg_opt.c         |   4 +
>  fftools/graph/filelauncher.c | 204 +++++++++++++++++++++++++++++++++++
>  fftools/graph/graphprint.c   |  50 ++++++++-
>  fftools/graph/graphprint.h   |  32 ++++++
>  9 files changed, 295 insertions(+), 5 deletions(-)
>  create mode 100644 fftools/graph/filelauncher.c

mingw64 on ubuntu

make -k
CC	fftools/graph/filelauncher.o
src/fftools/graph/filelauncher.c: In function ‘ff_open_html_in_browser’:
src/fftools/graph/filelauncher.c:42:24: error: implicit declaration of function ‘ShellExecuteA’ [-Werror=implicit-function-declaration]
   42 |         HINSTANCE rc = ShellExecuteA(NULL, "open", html_path, NULL, NULL, SW_SHOWNORMAL);
      |                        ^~~~~~~~~~~~~
src/fftools/graph/filelauncher.c:42:24: warning: initialization of ‘HINSTANCE’ {aka ‘struct HINSTANCE__ *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
cc1: some warnings being treated as errors
make: *** [src/ffbuild/common.mak:81: fftools/graph/filelauncher.o] Error 1
LD	ffprobe_g.exe
x86_64-w64-mingw32-gcc: error: fftools/resources/graph.html.o: No such file or directory
x86_64-w64-mingw32-gcc: error: fftools/resources/graph.css.o: No such file or directory
make: *** [Makefile:142: ffprobe_g.exe] Error 1
make: Target 'all' not remade because of errors.



[...]
-- 
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

Into a blind darkness they enter who follow after the Ignorance,
they as if into a greater darkness enter who devote themselves
to the Knowledge alone. -- Isha Upanishad

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

[-- Attachment #2: Type: text/plain, Size: 251 bytes --]

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing
  2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
                       ` (10 preceding siblings ...)
  2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 11/11] fftools/graphprint: Now, make it a Killer-Feature! softworkz
@ 2025-04-20 22:59     ` ffmpegagent
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 01/11] fftools/textformat: Formatting and whitespace changes softworkz
                         ` (11 more replies)
  11 siblings, 12 replies; 130+ messages in thread
From: ffmpegagent @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

Shortest cover letter for my longest-running FFmpeg patchset:

 * Apply
 * Build
 * Add the "-sg" switch to any FFmpeg command line
 * Press 'q' when you don't want to wait

SG = Show Graph

Documentation and examples can be found here:

https://github.com/softworkz/ffmpeg_output_apis/wiki


Version Updates
===============


V2
==

 * Rebased on top of Andreas' improvements
 * Applied changes from review (thanks, Andreas)


V3
==

 * Fixed all "new warnings"
 * Fixed out-of-tree building (thanks, Michael)


V4
==

 * Resolved merge conflict
 * Fixed build on MinGW (missing include due to WIN32_LEAN_AND_MEAN being
   defined) (thanks, Michael)

softworkz (11):
  fftools/textformat: Formatting and whitespace changes
  fftools/textformat: Quality improvements
  fftools/textformat: Introduce common header and deduplicate code
  fftools/tf_internal: Use ac_default_item_name
  fftools/textformat: Add function avtext_print_integer_flags()
  fftools/ffmpeg_filter: Move some declaration to new header file
  avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  fftools/resources: Add resource manager files
  fftools/ffmpeg_mux: Make ms_from_ost() inline
  fftools/graphprint: Add execution graph printing
  fftools/graphprint: Now, make it a Killer-Feature!

 doc/APIchanges                     |    3 +
 doc/ffmpeg.texi                    |   14 +
 ffbuild/common.mak                 |   28 +-
 fftools/Makefile                   |   22 +-
 fftools/ffmpeg.c                   |    4 +
 fftools/ffmpeg.h                   |    4 +
 fftools/ffmpeg_filter.c            |  195 +----
 fftools/ffmpeg_filter.h            |  234 ++++++
 fftools/ffmpeg_mux.h               |    2 +-
 fftools/ffmpeg_opt.c               |   17 +
 fftools/graph/filelauncher.c       |  205 +++++
 fftools/graph/graphprint.c         | 1146 ++++++++++++++++++++++++++++
 fftools/graph/graphprint.h         |   62 ++
 fftools/resources/.gitignore       |    4 +
 fftools/resources/Makefile         |   27 +
 fftools/resources/graph.css        |  353 +++++++++
 fftools/resources/graph.html       |   86 +++
 fftools/resources/resman.c         |  213 ++++++
 fftools/resources/resman.h         |   50 ++
 fftools/textformat/avtextformat.c  |  238 +++---
 fftools/textformat/avtextformat.h  |   53 +-
 fftools/textformat/avtextwriters.h |   11 +-
 fftools/textformat/tf_compact.c    |  121 +--
 fftools/textformat/tf_default.c    |   55 +-
 fftools/textformat/tf_flat.c       |   51 +-
 fftools/textformat/tf_ini.c        |   62 +-
 fftools/textformat/tf_internal.h   |   81 ++
 fftools/textformat/tf_json.c       |   56 +-
 fftools/textformat/tf_mermaid.c    |  658 ++++++++++++++++
 fftools/textformat/tf_mermaid.h    |   41 +
 fftools/textformat/tf_xml.c        |   68 +-
 fftools/textformat/tw_avio.c       |   16 +-
 fftools/textformat/tw_buffer.c     |    7 +-
 fftools/textformat/tw_stdout.c     |    8 +-
 libavfilter/avfilter.c             |    9 +
 libavfilter/avfilter.h             |   12 +
 36 files changed, 3670 insertions(+), 546 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h
 create mode 100644 fftools/graph/filelauncher.c
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h
 create mode 100644 fftools/textformat/tf_internal.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h


base-commit: 9e1162bdf1454d7ae3737429bcc6bd66e5da303a
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-66%2Fsoftworkz%2Fsubmit_print_execution_graph-v4
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-66/softworkz/submit_print_execution_graph-v4
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/66

Range-diff vs v3:

  1:  357859ee56 =  1:  dbd9193a0d fftools/textformat: Formatting and whitespace changes
  2:  9279d2f53d =  2:  1516e67a5b fftools/textformat: Quality improvements
  3:  d6734bd718 =  3:  7024548b92 fftools/textformat: Introduce common header and deduplicate code
  4:  8289c0ebf8 =  4:  4f1218b594 fftools/tf_internal: Use ac_default_item_name
  5:  a3fe7abcfe =  5:  c1fea3027a fftools/textformat: Add function avtext_print_integer_flags()
  6:  5b0ac30814 =  6:  28aeb7180d fftools/ffmpeg_filter: Move some declaration to new header file
  7:  5e3225c9df !  7:  d2ad11ac85 avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
     @@ doc/APIchanges: The last version increases of all libraries were on 2025-03-28
      +2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
      +  Add avfilter_link_get_hw_frames_ctx().
      +
     - 2025-04-16 - c818c67991 - libpostproc 59.1.100 - postprocess.h
     -   Deprecate PP_CPU_CAPS_3DNOW.
     + 2025-04-21 - xxxxxxxxxx - lavu 60.2.100 - log.h
     +   Add AV_CLASS_CATEGORY_HWDEVICE.
       
      
       ## libavfilter/avfilter.c ##
  8:  9881050f93 =  8:  9fa93efdda fftools/resources: Add resource manager files
  9:  f639a2e9ff =  9:  45b2a38592 fftools/ffmpeg_mux: Make ms_from_ost() inline
 10:  998d6a70c4 = 10:  0e52640aca fftools/graphprint: Add execution graph printing
 11:  8817dd0991 ! 11:  07a8d731ee fftools/graphprint: Now, make it a Killer-Feature!
     @@ fftools/graph/filelauncher.c (new)
      +
      +#if defined(_WIN32)
      +#  include <windows.h>
     ++#  include <shellapi.h>
      +#else
      +#  include <sys/time.h>
      +#  include <time.h>

-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 01/11] fftools/textformat: Formatting and whitespace changes
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
@ 2025-04-20 22:59       ` softworkz
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat: Quality improvements softworkz
                         ` (10 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c  | 104 +++++++++++++++--------------
 fftools/textformat/avtextformat.h  |  16 ++---
 fftools/textformat/avtextwriters.h |  11 ++-
 fftools/textformat/tf_compact.c    |  91 ++++++++++++++-----------
 fftools/textformat/tf_default.c    |  20 +++---
 fftools/textformat/tf_flat.c       |  26 ++++----
 fftools/textformat/tf_ini.c        |  36 +++++-----
 fftools/textformat/tf_json.c       |  10 +--
 fftools/textformat/tf_xml.c        |  30 +++++----
 9 files changed, 183 insertions(+), 161 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 9200b9b1ad..edbcd0b342 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -34,9 +34,9 @@
 #include "libavutil/opt.h"
 #include "avtextformat.h"
 
-#define SECTION_ID_NONE -1
+#define SECTION_ID_NONE (-1)
 
-#define SHOW_OPTIONAL_FIELDS_AUTO       -1
+#define SHOW_OPTIONAL_FIELDS_AUTO      (-1)
 #define SHOW_OPTIONAL_FIELDS_NEVER       0
 #define SHOW_OPTIONAL_FIELDS_ALWAYS      1
 
@@ -64,14 +64,14 @@ static const char *textcontext_get_formatter_name(void *p)
 
 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" },
+      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"}},
+      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 }
 };
 
@@ -125,14 +125,18 @@ void avtext_context_close(AVTextFormatContext **ptctx)
 }
 
 
-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)
+int avtext_context_open(AVTextFormatContext      **ptctx,
+                        const AVTextFormatter     *formatter,
+                        AVTextWriterContext       *writer_context,
+                        const char                *args,
+                        const 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;
@@ -200,7 +204,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash) {
+    if (show_data_hash)
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -211,7 +215,6 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             return ret;
         }
-    }
 
     /* validate replace string */
     {
@@ -224,7 +227,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             if (ret < 0) {
                 AVBPrint bp;
                 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
-                bprint_bytes(&bp, p0, p-p0),
+                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);
@@ -248,15 +251,13 @@ fail:
 }
 
 /* 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_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)
+void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
@@ -272,8 +273,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx,
 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;
+    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]++;
@@ -285,8 +287,7 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
     tctx->level--;
 }
 
-void avtext_print_integer(AVTextFormatContext *tctx,
-                                        const char *key, int64_t val)
+void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
     const struct AVTextFormatSection *section = tctx->section[tctx->level];
 
@@ -324,11 +325,9 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
             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);
+                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);
@@ -340,11 +339,10 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
             av_bprint_append_data(&dstbuf, p0, p-p0);
     }
 
-    if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
+    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);
@@ -352,7 +350,11 @@ end:
 }
 
 struct unit_value {
-    union { double d; int64_t i; } val;
+    union {
+        double  d;
+        int64_t i;
+    } val;
+
     const char *unit;
 };
 
@@ -402,8 +404,9 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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 : "");
+                    prefix_string, tctx->show_value_unit ? uv.unit : "");
     }
 
     return buf;
@@ -427,8 +430,8 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 
     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)))
+            && (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)) {
@@ -440,11 +443,10 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
             if (ret < 0) goto end;
             tctx->formatter->print_string(tctx, key1, val1);
         end:
-            if (ret < 0) {
+            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 {
@@ -457,8 +459,7 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
     return ret;
 }
 
-void avtext_print_rational(AVTextFormatContext *tctx,
-                                         const char *key, AVRational q, char sep)
+void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep)
 {
     char buf[44];
     snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
@@ -466,7 +467,7 @@ void avtext_print_rational(AVTextFormatContext *tctx,
 }
 
 void avtext_print_time(AVTextFormatContext *tctx, const char *key,
-                              int64_t ts, const AVRational *time_base, int is_duration)
+                       int64_t ts, const AVRational *time_base, int is_duration)
 {
     char buf[128];
 
@@ -484,15 +485,14 @@ void avtext_print_time(AVTextFormatContext *tctx, const char *key,
 
 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)) {
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0))
         avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
-    } else {
+    else
         avtext_print_integer(tctx, key, ts);
-    }
 }
 
 void avtext_print_data(AVTextFormatContext *tctx, const char *name,
-                              const uint8_t *data, int size)
+                       const uint8_t *data, int size)
 {
     AVBPrint bp;
     int offset = 0, l, i;
@@ -520,12 +520,13 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 }
 
 void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
-                                   const uint8_t *data, int size)
+                            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));
@@ -551,7 +552,7 @@ void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
             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 --;
+            size--;
         }
         av_bprintf(&bp, "\n");
         offset += offset_add;
@@ -641,7 +642,8 @@ fail:
     return ret;
 }
 
-static const AVTextFormatter *registered_formatters[7+1];
+static const AVTextFormatter *registered_formatters[10 + 1];
+
 static void formatters_register_all(void)
 {
     static int initialized;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c2c56dc1a7..c598af3450 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -86,17 +86,17 @@ typedef struct AVTextFormatter {
 #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
+    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
+    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
+    const AVTextFormatSection *sections; ///< array containing all sections
+    int nb_sections;                   ///< number of sections
 
-    int level;                      ///< current level, starting from 0
+    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];
diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index c99d6b3548..34db3f1832 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -37,11 +37,11 @@ typedef struct AVTextWriter {
     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, ...);
+    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 {
@@ -49,7 +49,6 @@ typedef struct AVTextWriterContext {
     const AVTextWriter *writer;
     const char *name;
     void *priv;                     ///< private data for use by the writer
-
 } AVTextWriterContext;
 
 
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index 31bfc81513..d4ac296a42 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -58,10 +58,10 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 
     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 '\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)
@@ -78,6 +78,7 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 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)
@@ -114,16 +115,16 @@ typedef struct 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},
+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);
@@ -139,10 +140,13 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
     }
     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 {
+    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);
     }
@@ -162,8 +166,8 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
     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))))) {
+            (!(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 */
@@ -171,10 +175,10 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
         AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
 
         compact->nested_section[wctx->level] = 1;
-        compact->has_nested_elems[wctx->level-1] = 1;
+        compact->has_nested_elems[wctx->level - 1] = 1;
 
         av_bprintf(section_pbuf, "%s%s",
-                   wctx->section_pbuf[wctx->level-1].str, element_name);
+                   wctx->section_pbuf[wctx->level - 1].str, element_name);
 
         if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
             // add /TYPE to prefix
@@ -185,30 +189,33 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
                 char c =
                     (*p >= '0' && *p <= '9') ||
                     (*p >= 'a' && *p <= 'z') ||
-                    (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
+                    (*p >= 'A' && *p <= 'Z')
+                    ? (char)(char)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];
+        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])
+        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)))
+            !(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)
 {
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     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)))
+        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
         writer_w8(wctx, '\n');
 }
 
@@ -217,9 +224,12 @@ static void compact_print_str(AVTextFormatContext *wctx, const char *key, const
     CompactContext *compact = wctx->priv;
     AVBPrint buf;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
@@ -229,9 +239,12 @@ static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_
 {
     CompactContext *compact = wctx->priv;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
 }
 
@@ -253,15 +266,15 @@ const AVTextFormatter avtextformatter_compact = {
 #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},
+    { "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);
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 86582829e4..2c5047eafd 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -56,11 +56,11 @@ typedef struct DefaultContext {
 #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},
+    { "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);
@@ -69,7 +69,7 @@ DEFINE_FORMATTER_CLASS(default);
 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++)
+    for (i = 0; src[i] && i < dst_size - 1; i++)
         dst[i] = av_toupper(src[i]);
     dst[i] = 0;
     return dst;
@@ -85,10 +85,10 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 
     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))) {
+        !(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,
+                   wctx->section_pbuf[wctx->level - 1].str,
                    upcase_string(buf, sizeof(buf),
                                  av_x_if_null(section->element_name, section->name)));
     }
@@ -96,7 +96,7 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
     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)))
+    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));
 }
 
@@ -109,7 +109,7 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     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)))
+    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));
 }
 
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index 919d44bc6b..f692971bcc 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -57,12 +57,12 @@ typedef struct 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},
+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);
@@ -126,16 +126,18 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
     av_bprint_clear(buf);
     if (!parent_section)
         return;
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+
+    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))) {
+        !(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];
+            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);
         }
     }
@@ -166,6 +168,6 @@ const AVTextFormatter avtextformatter_flat = {
     .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,
+    .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
index d8099ff92e..88add0819a 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -64,9 +64,9 @@ typedef struct INIContext {
 #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},
+    { "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);
@@ -74,9 +74,9 @@ DEFINE_FORMATTER_CLASS(ini);
 static char *ini_escape_str(AVBPrint *dst, const char *src)
 {
     int i = 0;
-    char c = 0;
+    char c;
 
-    while (c = src[i++]) {
+    while ((c = src[i++])) {
         switch (c) {
         case '\b': av_bprintf(dst, "%s", "\\b"); break;
         case '\f': av_bprintf(dst, "%s", "\\f"); break;
@@ -84,9 +84,11 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
         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);
+        case '#':
+        case '=':
+        case ':':
+            av_bprint_chars(dst, '\\', 1);
+            /* fallthrough */
         default:
             if ((unsigned char)c < 32)
                 av_bprintf(dst, "\\x00%02x", c & 0xff);
@@ -112,23 +114,23 @@ static void ini_print_section_header(AVTextFormatContext *wctx, const void *data
         return;
     }
 
-    if (wctx->nb_item[wctx->level-1])
+    if (wctx->nb_item[wctx->level - 1])
         writer_w8(wctx, '\n');
 
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+    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))) {
+        !(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);
+            unsigned 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, ".%u", n);
         }
     }
 
-    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
         writer_printf(wctx, "[%s]\n", buf->str);
 }
 
@@ -154,6 +156,6 @@ const AVTextFormatter avtextformatter_ini = {
     .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,
+    .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
index c26a912435..b61d3740c6 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -56,9 +56,9 @@ typedef struct 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 },
+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 }
 };
 
@@ -76,8 +76,8 @@ static av_cold int json_init(AVTextFormatContext *wctx)
 
 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};
+    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++) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index 6c89d01e9d..befb39246d 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -58,11 +58,11 @@ typedef struct XMLContext {
 #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},
+    { "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);
@@ -104,8 +104,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 
         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 : "");
+                      xml->fully_qualified ? "ffprobe:" : "",
+                      xml->fully_qualified ? qual : "");
         return;
     }
 
@@ -115,12 +115,13 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
     }
 
     if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
-        wctx->level && wctx->nb_item[wctx->level-1])
+        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_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;
@@ -131,7 +132,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
         }
         writer_printf(wctx, ">\n", section->name);
     } else {
-        XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "<%s ", section->name);
         xml->within_tag = 1;
     }
 }
@@ -148,7 +150,8 @@ static void xml_print_section_footer(AVTextFormatContext *wctx)
         writer_put_str(wctx, "/>\n");
         xml->indent_level--;
     } else {
-        XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "</%s>\n", section->name);
         xml->indent_level--;
     }
 }
@@ -195,7 +198,8 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
     av_bprint_finalize(&buf, NULL);
 }
 
-static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
+static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
     xml_print_value(wctx, key, value, 0, 0);
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat: Quality improvements
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 01/11] fftools/textformat: Formatting and whitespace changes softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-21 17:16         ` Stefano Sabatini
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 03/11] fftools/textformat: Introduce common header and deduplicate code softworkz
                         ` (9 subsequent siblings)
  11 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 111 +++++++++++++++++++-----------
 fftools/textformat/avtextformat.h |   6 +-
 fftools/textformat/tf_default.c   |   8 ++-
 fftools/textformat/tf_ini.c       |   2 +-
 fftools/textformat/tf_json.c      |   8 ++-
 fftools/textformat/tf_xml.c       |   3 -
 fftools/textformat/tw_avio.c      |   9 ++-
 7 files changed, 93 insertions(+), 54 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index edbcd0b342..893b11298e 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
 
 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++)
+    for (unsigned i = 0; i < ubuf_size; i++)
         av_bprintf(bp, "%02X", ubuf[i]);
 }
 
@@ -141,7 +140,10 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
     AVTextFormatContext *tctx;
     int i, ret = 0;
 
-    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
+    if (!ptctx || !formatter)
+        return AVERROR(EINVAL);
+
+    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
@@ -213,25 +215,26 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
                     av_log(NULL, AV_LOG_ERROR, " %s", n);
                 av_log(NULL, AV_LOG_ERROR, "\n");
             }
-            return ret;
+            goto fail;
         }
 
     /* validate replace string */
     {
-        const uint8_t *p = tctx->string_validation_replacement;
-        const uint8_t *endp = p + strlen(p);
+        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
+        const uint8_t *endp = p + strlen((const char *)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);
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
                 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;
+                av_bprint_finalize(&bp, NULL);
+                goto fail;
             }
         }
     }
@@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
 
 void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
+    if (section_id < 0 || section_id >= tctx->nb_sections)
+        return;
+
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
 
@@ -272,6 +278,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
 
 void avtext_print_section_footer(AVTextFormatContext *tctx)
 {
+    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
     int section_id = tctx->section[tctx->level]->id;
     int parent_section_id = tctx->level
         ? tctx->section[tctx->level - 1]->id
@@ -289,7 +298,12 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
+
+    if (!key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
+    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);
@@ -299,24 +313,25 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
 
 static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
 {
-    const uint8_t *p, *endp;
+    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
     AVBPrint dstbuf;
+    AVBPrint bp;
     int invalid_chars_nb = 0, ret = 0;
 
+    *dstp = NULL;
     av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
 
-    endp = src + strlen(src);
-    for (p = src; *p;) {
-        uint32_t code;
+    endp = srcp + strlen(src);
+    for (p = srcp; *p;) {
+        int32_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);
+            av_bprint_clear(&bp);
+            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;
         }
 
@@ -336,7 +351,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
         }
 
         if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
-            av_bprint_append_data(&dstbuf, p0, p-p0);
+            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
     }
 
     if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
@@ -346,6 +361,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
 end:
     av_bprint_finalize(&dstbuf, dstp);
+    av_bprint_finalize(&bp, NULL);
     return ret;
 }
 
@@ -358,17 +374,18 @@ struct unit_value {
     const char *unit;
 };
 
-static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
+static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
 {
     double vald;
-    int64_t vali;
+    int64_t vali = 0;
     int show_float = 0;
 
     if (uv.unit == unit_second_str) {
         vald = uv.val.d;
         show_float = 1;
     } else {
-        vald = vali = uv.val.i;
+        vald = (double)uv.val.i;
+        vali = uv.val.i;
     }
 
     if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
@@ -387,17 +404,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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);
+                index = (int64_t)(log2(vald) / 10);
+                index = av_clip64(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);
+                index = (int64_t)(log10(vald) / 3);
+                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
                 vald /= si_prefixes[index].dec_val;
                 prefix_string = si_prefixes[index].dec_str;
             }
-            vali = vald;
+            vali = (int64_t)vald;
         }
 
         if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
@@ -425,9 +442,14 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
 
 int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
     int ret = 0;
 
+    if (!key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return AVERROR(EINVAL);
+
+    section = tctx->section[tctx->level];
+
     if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
         (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
             && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
@@ -469,12 +491,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
 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);
+        char buf[128];
+        double d = av_q2d(*time_base) * ts;
         struct unit_value uv;
         uv.val.d = d;
         uv.unit = unit_second_str;
@@ -495,7 +516,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
                        const uint8_t *data, int size)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -522,25 +544,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 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 };
+    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
+    int len;
 
     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);
+    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
+    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
     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)
+                           uint8_t *data, int size, const char *format,
+                           int columns, int bytes, int offset_add)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
+
+    if (!name || !data || !format || columns <= 0 || bytes <= 0)
+        return;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -606,12 +632,15 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
     AVTextWriterContext *wctx;
     int ret = 0;
 
-    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
+    if (!pwctx || !writer)
+        return AVERROR(EINVAL);
+
+    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
-    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
+    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c598af3450..aea691f351 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -21,9 +21,7 @@
 #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"
@@ -103,7 +101,7 @@ struct AVTextFormatContext {
     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];
+    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
     AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
                                                   ///  used by various formatters
 
@@ -124,7 +122,7 @@ struct AVTextFormatContext {
 #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 2c5047eafd..ad97173b0b 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -68,9 +68,10 @@ 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;
+    unsigned i;
+
     for (i = 0; src[i] && i < dst_size - 1; i++)
-        dst[i] = av_toupper(src[i]);
+        dst[i] = (char)av_toupper(src[i]);
     dst[i] = 0;
     return dst;
 }
@@ -106,6 +107,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     const struct AVTextFormatSection *section = wctx->section[wctx->level];
     char buf[32];
 
+    if (!section)
+        return;
+
     if (def->noprint_wrappers || def->nested_section[wctx->level])
         return;
 
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index 88add0819a..dd77d0e8bf 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
             /* fallthrough */
         default:
             if ((unsigned char)c < 32)
-                av_bprintf(dst, "\\x00%02x", c & 0xff);
+                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
             else
                 av_bprint_chars(dst, c, 1);
             break;
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index b61d3740c6..e86cdbf5d9 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
     static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
     const char *p;
 
+    if (!src) {
+        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source string\n");
+        return NULL;
+    }
+
     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);
+            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
         } else {
             av_bprint_chars(dst, *p, 1);
         }
@@ -105,6 +110,7 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
         wctx->section[wctx->level-1] : NULL;
 
     if (wctx->level && wctx->nb_item[wctx->level-1])
+    if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
     if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index befb39246d..28abfc6400 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -18,10 +18,7 @@
  * 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"
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
index 6034f74ec9..d1b494b7b4 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -53,7 +53,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
 static void io_put_str(AVTextWriterContext *wctx, const char *str)
 {
     IOWriterContext *ctx = wctx->priv;
-    avio_write(ctx->avio_context, str, strlen(str));
+    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
 static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
@@ -78,10 +78,12 @@ const AVTextWriter avtextwriter_avio = {
 
 int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
 {
+    if (!output_filename || !output_filename[0])
+        return AVERROR(EINVAL);
+
     IOWriterContext *ctx;
     int ret;
 
-
     ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
     if (ret < 0)
         return ret;
@@ -103,6 +105,9 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
 
 int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
 {
+    if (!pwctx || !avio_ctx)
+        return AVERROR(EINVAL);
+
     IOWriterContext *ctx;
     int 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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 03/11] fftools/textformat: Introduce common header and deduplicate code
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 01/11] fftools/textformat: Formatting and whitespace changes softworkz
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat: Quality improvements softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-21 17:28         ` Stefano Sabatini
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use ac_default_item_name softworkz
                         ` (8 subsequent siblings)
  11 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextwriters.h |  2 +-
 fftools/textformat/tf_compact.c    | 32 ++++-------
 fftools/textformat/tf_default.c    | 27 +++-------
 fftools/textformat/tf_flat.c       | 25 +++------
 fftools/textformat/tf_ini.c        | 24 +++------
 fftools/textformat/tf_internal.h   | 85 ++++++++++++++++++++++++++++++
 fftools/textformat/tf_json.c       | 38 +++++--------
 fftools/textformat/tf_xml.c        | 35 +++++-------
 fftools/textformat/tw_avio.c       |  7 +--
 fftools/textformat/tw_buffer.c     |  7 +--
 fftools/textformat/tw_stdout.c     |  8 +--
 11 files changed, 149 insertions(+), 141 deletions(-)
 create mode 100644 fftools/textformat/tf_internal.h

diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index 34db3f1832..fd6da747eb 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -41,7 +41,7 @@ typedef struct AVTextWriter {
     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, ...);
+    void (*writer_printf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
 } AVTextWriter;
 
 typedef struct AVTextWriterContext {
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index d4ac296a42..e52888239e 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -28,23 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 
 /* Compact output */
@@ -157,9 +141,12 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
+
     compact->terminate_line[wctx->level] = 1;
     compact->has_nested_elems[wctx->level] = 0;
 
@@ -210,8 +197,11 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
 
 static void compact_print_section_footer(AVTextFormatContext *wctx)
 {
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     CompactContext *compact = wctx->priv;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (!compact->nested_section[wctx->level] &&
         compact->terminate_line[wctx->level] &&
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index ad97173b0b..019bda9d44 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -27,21 +27,7 @@
 #include "avtextformat.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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -80,9 +66,11 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(&wctx->section_pbuf[wctx->level]);
     if (parent_section &&
@@ -104,7 +92,8 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 static void default_print_section_footer(AVTextFormatContext *wctx)
 {
     DefaultContext *def = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
     char buf[32];
 
     if (!section)
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index f692971bcc..d5517f109b 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -28,22 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
-
+#include "tf_internal.h"
 
 /* Flat output */
 
@@ -118,9 +103,11 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     /* build section header */
     av_bprint_clear(buf);
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index dd77d0e8bf..8959785295 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -28,21 +28,7 @@
 
 #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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -104,9 +90,11 @@ 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(buf);
     if (!parent_section) {
diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
new file mode 100644
index 0000000000..7b326328cb
--- /dev/null
+++ b/fftools/textformat/tf_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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
+ */
+
+/**
+ * @file
+ * Internal utilities for text formatters.
+ */
+
+#ifndef FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+#define FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+
+#include "avtextformat.h"
+
+#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                    \
+}
+
+
+/**
+ * Safely validate and access a section at a given level
+ */
+static inline const AVTextFormatSection *tf_get_section(AVTextFormatContext *tfc, int level)
+{
+    if (!tfc || level < 0 || level >= SECTION_MAX_NB_LEVELS || !tfc->section[level]) {
+        if (tfc)
+            av_log(tfc, AV_LOG_ERROR, "Invalid section access at level %d\n", level);
+        return NULL;
+    }
+    return tfc->section[level];
+}
+
+/**
+ * Safely access the parent section
+ */
+static inline const AVTextFormatSection *tf_get_parent_section(AVTextFormatContext *tfc, int level)
+{
+    if (level <= 0)
+        return NULL;
+
+    return tf_get_section(tfc, level - 1);
+}
+
+static inline void writer_w8(AVTextFormatContext *wctx, int b)
+{
+    wctx->writer->writer->writer_w8(wctx->writer, b);
+}
+
+static inline void writer_put_str(AVTextFormatContext *wctx, const char *str)
+{
+    wctx->writer->writer->writer_put_str(wctx->writer, str);
+}
+
+static inline void writer_printf(AVTextFormatContext *wctx, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    wctx->writer->writer->writer_printf(wctx->writer, fmt, args);
+    va_end(args);
+}
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_INTERNAL_H */
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index e86cdbf5d9..8072fa44a4 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -27,22 +27,7 @@
 #include "avtextformat.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                    \
-}
-
+#include "tf_internal.h"
 
 /* JSON output */
 
@@ -103,13 +88,14 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
 
 static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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])
+    if (!section)
+        return;
+
     if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
@@ -143,8 +129,11 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 
 static void json_print_section_footer(AVTextFormatContext *wctx)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         json->indent_level--;
@@ -177,9 +166,8 @@ static inline void json_print_item_str(AVTextFormatContext *wctx,
 
 static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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);
@@ -190,9 +178,8 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 
 static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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))
@@ -216,4 +203,3 @@ const AVTextFormatter avtextformatter_json = {
     .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
index 28abfc6400..6b09e09ab4 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -25,21 +25,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 /* XML output */
 
@@ -90,9 +76,11 @@ static av_cold int xml_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
@@ -138,7 +126,10 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 static void xml_print_section_footer(AVTextFormatContext *wctx)
 {
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
@@ -158,7 +149,10 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
 {
     AVBPrint buf;
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
 
@@ -216,4 +210,3 @@ const AVTextFormatter avtextformatter_xml = {
     .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
index d1b494b7b4..48868ebf5d 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -56,14 +56,11 @@ static void io_put_str(AVTextWriterContext *wctx, const char *str)
     avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
-static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void io_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     IOWriterContext *ctx = wctx->priv;
-    va_list ap;
 
-    va_start(ap, fmt);
-    avio_vprintf(ctx->avio_context, fmt, ap);
-    va_end(ap);
+    avio_vprintf(ctx->avio_context, fmt, vl);
 }
 
 
diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
index f8b38414a6..f861722247 100644
--- a/fftools/textformat/tw_buffer.c
+++ b/fftools/textformat/tw_buffer.c
@@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
     av_bprintf(ctx->buffer, "%s", str);
 }
 
-static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     BufferWriterContext *ctx = wctx->priv;
 
-    va_list vargs;
-    va_start(vargs, fmt);
-    av_vbprintf(ctx->buffer, fmt, vargs);
-    va_end(vargs);
+    av_vbprintf(ctx->buffer, fmt, vl);
 }
 
 
diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
index 23de6f671f..dace55f38a 100644
--- a/fftools/textformat/tw_stdout.c
+++ b/fftools/textformat/tw_stdout.c
@@ -53,13 +53,9 @@ static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
     printf("%s", str);
 }
 
-static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
-    va_list ap;
-
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
+    vprintf(fmt, vl);
 }
 
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use ac_default_item_name
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
                         ` (2 preceding siblings ...)
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 03/11] fftools/textformat: Introduce common header and deduplicate code softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-21 17:31         ` Stefano Sabatini
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 05/11] fftools/textformat: Add function avtext_print_integer_flags() softworkz
                         ` (7 subsequent siblings)
  11 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/tf_internal.h | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
index 7b326328cb..e145bc83bb 100644
--- a/fftools/textformat/tf_internal.h
+++ b/fftools/textformat/tf_internal.h
@@ -29,13 +29,9 @@
 #include "avtextformat.h"
 
 #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,                  \
+    .item_name  = av_default_item_name,             \
     .option     = name##_options                    \
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 05/11] fftools/textformat: Add function avtext_print_integer_flags()
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
                         ` (3 preceding siblings ...)
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use ac_default_item_name softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-23 22:56         ` Stefano Sabatini
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 06/11] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
                         ` (6 subsequent siblings)
  11 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This function works analog to the avtext_print_string() which already
has a flags parameter.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 21 +++++++++++++++++++++
 fftools/textformat/avtextformat.h |  2 ++
 2 files changed, 23 insertions(+)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 893b11298e..d2d84c4f1d 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -311,6 +311,27 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
     }
 }
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags)
+{
+    const AVTextFormatSection *section;
+
+    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
+    section = tctx->section[tctx->level];
+
+    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;
+
+    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, *srcp = (const uint8_t *)src;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index aea691f351..16cd9b214f 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -139,6 +139,8 @@ void avtext_print_section_footer(AVTextFormatContext *tctx);
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags);
+
 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);
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 06/11] fftools/ffmpeg_filter: Move some declaration to new header file
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
                         ` (4 preceding siblings ...)
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 05/11] fftools/textformat: Add function avtext_print_integer_flags() softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 07/11] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
                         ` (5 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

to allow filtergraph printing to access the information.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_filter.c | 190 +-------------------------------
 fftools/ffmpeg_filter.h | 234 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+), 189 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index d314aec206..eab9487f97 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ffmpeg.h"
+#include "ffmpeg_filter.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -42,44 +43,6 @@
 // FIXME private header, used for mid_pred()
 #include "libavcodec/mathops.h"
 
-typedef struct FilterGraphPriv {
-    FilterGraph      fg;
-
-    // name used for logging
-    char             log_name[32];
-
-    int              is_simple;
-    // true when the filtergraph contains only meta filters
-    // that do not modify the frame data
-    int              is_meta;
-    // source filters are present in the graph
-    int              have_sources;
-    int              disable_conversions;
-
-    unsigned         nb_outputs_done;
-
-    const char      *graph_desc;
-
-    int              nb_threads;
-
-    // frame for temporarily holding output from the filtergraph
-    AVFrame         *frame;
-    // frame for sending output to the encoder
-    AVFrame         *frame_enc;
-
-    Scheduler       *sch;
-    unsigned         sch_idx;
-} FilterGraphPriv;
-
-static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
-{
-    return (FilterGraphPriv*)fg;
-}
-
-static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
-{
-    return (const FilterGraphPriv*)fg;
-}
 
 // data that is local to the filter thread and not visible outside of it
 typedef struct FilterGraphThread {
@@ -102,157 +65,6 @@ typedef struct FilterGraphThread {
     uint8_t         *eof_out;
 } FilterGraphThread;
 
-typedef struct InputFilterPriv {
-    InputFilter         ifilter;
-
-    InputFilterOptions  opts;
-
-    int                 index;
-
-    AVFilterContext    *filter;
-
-    // used to hold submitted input
-    AVFrame            *frame;
-
-    /* for filters that are not yet bound to an input stream,
-     * this stores the input linklabel, if any */
-    uint8_t            *linklabel;
-
-    // filter data type
-    enum AVMediaType    type;
-    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
-    // same as type otherwise
-    enum AVMediaType    type_src;
-
-    int                 eof;
-    int                 bound;
-    int                 drop_warned;
-    uint64_t            nb_dropped;
-
-    // parameters configured for this input
-    int                 format;
-
-    int                 width, height;
-    AVRational          sample_aspect_ratio;
-    enum AVColorSpace   color_space;
-    enum AVColorRange   color_range;
-
-    int                 sample_rate;
-    AVChannelLayout     ch_layout;
-
-    AVRational          time_base;
-
-    AVFrameSideData   **side_data;
-    int                 nb_side_data;
-
-    AVFifo             *frame_queue;
-
-    AVBufferRef        *hw_frames_ctx;
-
-    int                 displaymatrix_present;
-    int                 displaymatrix_applied;
-    int32_t             displaymatrix[9];
-
-    int                 downmixinfo_present;
-    AVDownmixInfo       downmixinfo;
-
-    struct {
-        AVFrame *frame;
-
-        int64_t last_pts;
-        int64_t end_pts;
-
-        /// marks if sub2video_update should force an initialization
-        unsigned int initialize;
-    } sub2video;
-} InputFilterPriv;
-
-static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
-{
-    return (InputFilterPriv*)ifilter;
-}
-
-typedef struct FPSConvContext {
-    AVFrame          *last_frame;
-    /* number of frames emitted by the video-encoding sync code */
-    int64_t           frame_number;
-    /* history of nb_frames_prev, i.e. the number of times the
-     * previous frame was duplicated by vsync code in recent
-     * do_video_out() calls */
-    int64_t           frames_prev_hist[3];
-
-    uint64_t          dup_warning;
-
-    int               last_dropped;
-    int               dropped_keyframe;
-
-    enum VideoSyncMethod vsync_method;
-
-    AVRational        framerate;
-    AVRational        framerate_max;
-    const AVRational *framerate_supported;
-    int               framerate_clip;
-} FPSConvContext;
-
-typedef struct OutputFilterPriv {
-    OutputFilter            ofilter;
-
-    int                     index;
-
-    void                   *log_parent;
-    char                    log_name[32];
-
-    char                   *name;
-
-    AVFilterContext        *filter;
-
-    /* desired output stream properties */
-    int                     format;
-    int                     width, height;
-    int                     sample_rate;
-    AVChannelLayout         ch_layout;
-    enum AVColorSpace       color_space;
-    enum AVColorRange       color_range;
-
-    AVFrameSideData       **side_data;
-    int                     nb_side_data;
-
-    // time base in which the output is sent to our downstream
-    // does not need to match the filtersink's timebase
-    AVRational              tb_out;
-    // at least one frame with the above timebase was sent
-    // to our downstream, so it cannot change anymore
-    int                     tb_out_locked;
-
-    AVRational              sample_aspect_ratio;
-
-    AVDictionary           *sws_opts;
-    AVDictionary           *swr_opts;
-
-    // those are only set if no format is specified and the encoder gives us multiple options
-    // They point directly to the relevant lists of the encoder.
-    const int              *formats;
-    const AVChannelLayout  *ch_layouts;
-    const int              *sample_rates;
-    const enum AVColorSpace *color_spaces;
-    const enum AVColorRange *color_ranges;
-
-    AVRational              enc_timebase;
-    int64_t                 trim_start_us;
-    int64_t                 trim_duration_us;
-    // offset for output timestamps, in AV_TIME_BASE_Q
-    int64_t                 ts_offset;
-    int64_t                 next_pts;
-    FPSConvContext          fps;
-
-    unsigned                flags;
-} OutputFilterPriv;
-
-static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
-{
-    return (OutputFilterPriv*)ofilter;
-}
-
 typedef struct FilterCommand {
     char *target;
     char *command;
diff --git a/fftools/ffmpeg_filter.h b/fftools/ffmpeg_filter.h
new file mode 100644
index 0000000000..94b94beece
--- /dev/null
+++ b/fftools/ffmpeg_filter.h
@@ -0,0 +1,234 @@
+/*
+ * 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_FFMPEG_FILTER_H
+#define FFTOOLS_FFMPEG_FILTER_H
+
+#include "ffmpeg.h"
+
+#include <stdint.h>
+
+#include "ffmpeg_sched.h"
+#include "sync_queue.h"
+
+#include "libavfilter/avfilter.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/dict.h"
+#include "libavutil/fifo.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/downmix_info.h"
+
+typedef struct FilterGraphPriv {
+    FilterGraph      fg;
+
+    // name used for logging
+    char             log_name[32];
+
+    int              is_simple;
+    // true when the filtergraph contains only meta filters
+    // that do not modify the frame data
+    int              is_meta;
+    // source filters are present in the graph
+    int              have_sources;
+    int              disable_conversions;
+
+    unsigned         nb_outputs_done;
+
+    const char      *graph_desc;
+
+    int              nb_threads;
+
+    // frame for temporarily holding output from the filtergraph
+    AVFrame         *frame;
+    // frame for sending output to the encoder
+    AVFrame         *frame_enc;
+
+    Scheduler       *sch;
+    unsigned         sch_idx;
+
+    AVBPrint graph_print_buf;
+
+} FilterGraphPriv;
+
+static inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
+{
+    return (FilterGraphPriv*)fg;
+}
+
+static inline const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
+{
+    return (const FilterGraphPriv*)fg;
+}
+
+typedef struct InputFilterPriv {
+    InputFilter         ifilter;
+
+    InputFilterOptions  opts;
+
+    int                 index;
+
+    AVFilterContext    *filter;
+
+    // used to hold submitted input
+    AVFrame            *frame;
+
+    /* for filters that are not yet bound to an input stream,
+     * this stores the input linklabel, if any */
+    uint8_t            *linklabel;
+
+    // filter data type
+    enum AVMediaType    type;
+    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
+    // same as type otherwise
+    enum AVMediaType    type_src;
+
+    int                 eof;
+    int                 bound;
+    int                 drop_warned;
+    uint64_t            nb_dropped;
+
+    // parameters configured for this input
+    int                 format;
+
+    int                 width, height;
+    AVRational          sample_aspect_ratio;
+    enum AVColorSpace   color_space;
+    enum AVColorRange   color_range;
+
+    int                 sample_rate;
+    AVChannelLayout     ch_layout;
+
+    AVRational          time_base;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
+    AVFifo             *frame_queue;
+
+    AVBufferRef        *hw_frames_ctx;
+
+    int                 displaymatrix_present;
+    int                 displaymatrix_applied;
+    int32_t             displaymatrix[9];
+
+    int                 downmixinfo_present;
+    AVDownmixInfo       downmixinfo;
+
+    struct {
+        AVFrame *frame;
+
+        int64_t last_pts;
+        int64_t end_pts;
+
+        /// marks if sub2video_update should force an initialization
+        unsigned int initialize;
+    } sub2video;
+} InputFilterPriv;
+
+static inline InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
+{
+    return (InputFilterPriv*)ifilter;
+}
+
+typedef struct FPSConvContext {
+    AVFrame          *last_frame;
+    /* number of frames emitted by the video-encoding sync code */
+    int64_t           frame_number;
+    /* history of nb_frames_prev, i.e. the number of times the
+     * previous frame was duplicated by vsync code in recent
+     * do_video_out() calls */
+    int64_t           frames_prev_hist[3];
+
+    uint64_t          dup_warning;
+
+    int               last_dropped;
+    int               dropped_keyframe;
+
+    enum VideoSyncMethod vsync_method;
+
+    AVRational        framerate;
+    AVRational        framerate_max;
+    const AVRational *framerate_supported;
+    int               framerate_clip;
+} FPSConvContext;
+
+
+typedef struct OutputFilterPriv {
+    OutputFilter            ofilter;
+
+    int                     index;
+
+    void                   *log_parent;
+    char                    log_name[32];
+
+    char                   *name;
+
+    AVFilterContext        *filter;
+
+    /* desired output stream properties */
+    int                     format;
+    int                     width, height;
+    int                     sample_rate;
+    AVChannelLayout         ch_layout;
+    enum AVColorSpace       color_space;
+    enum AVColorRange       color_range;
+
+    AVFrameSideData       **side_data;
+    int                     nb_side_data;
+
+    // time base in which the output is sent to our downstream
+    // does not need to match the filtersink's timebase
+    AVRational              tb_out;
+    // at least one frame with the above timebase was sent
+    // to our downstream, so it cannot change anymore
+    int                     tb_out_locked;
+
+    AVRational              sample_aspect_ratio;
+
+    AVDictionary           *sws_opts;
+    AVDictionary           *swr_opts;
+
+    // those are only set if no format is specified and the encoder gives us multiple options
+    // They point directly to the relevant lists of the encoder.
+    const int              *formats;
+    const AVChannelLayout  *ch_layouts;
+    const int              *sample_rates;
+    const enum AVColorSpace *color_spaces;
+    const enum AVColorRange *color_ranges;
+
+    AVRational              enc_timebase;
+    int64_t                 trim_start_us;
+    int64_t                 trim_duration_us;
+    // offset for output timestamps, in AV_TIME_BASE_Q
+    int64_t                 ts_offset;
+    int64_t                 next_pts;
+    FPSConvContext          fps;
+
+    unsigned                flags;
+} OutputFilterPriv;
+
+static inline OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
+{
+    return (OutputFilterPriv*)ofilter;
+}
+
+#endif /* FFTOOLS_FFMPEG_FILTER_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 07/11] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
                         ` (5 preceding siblings ...)
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 06/11] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 08/11] fftools/resources: Add resource manager files softworkz
                         ` (4 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/APIchanges         |  3 +++
 libavfilter/avfilter.c |  9 +++++++++
 libavfilter/avfilter.h | 12 ++++++++++++
 3 files changed, 24 insertions(+)

diff --git a/doc/APIchanges b/doc/APIchanges
index 75d66f87f3..d0869561f3 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
+  Add avfilter_link_get_hw_frames_ctx().
+
 2025-04-21 - xxxxxxxxxx - lavu 60.2.100 - log.h
   Add AV_CLASS_CATEGORY_HWDEVICE.
 
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index 64c1075c40..c76d43a215 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -989,6 +989,15 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx)
     return pads[pad_idx].type;
 }
 
+AVBufferRef *avfilter_link_get_hw_frames_ctx(AVFilterLink *link)
+{
+    FilterLink *plink = ff_filter_link(link);
+    if (plink->hw_frames_ctx)
+        return av_buffer_ref(plink->hw_frames_ctx);
+
+    return NULL;
+}
+
 static int default_filter_frame(AVFilterLink *link, AVFrame *frame)
 {
     return ff_filter_frame(link->dst->outputs[0], frame);
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index a89d3cf658..f85929dc5c 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -96,6 +96,18 @@ const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
  */
 enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
 
+/**
+ * Get the hardware frames context of a filter link.
+ *
+ * @param link an AVFilterLink
+ *
+ * @return a ref-counted copy of the link's hw_frames_ctx field if there is
+ *         a hardware frames context associated with the link or NULL otherwise.
+ *         The returned AVBufferRef needs to be released with av_buffer_unref()
+ *         when it is no longer used.
+ */
+AVBufferRef* avfilter_link_get_hw_frames_ctx(AVFilterLink *link);
+
 /**
  * Lists of formats / etc. supported by an end of a link.
  *
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 08/11] fftools/resources: Add resource manager files
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
                         ` (6 preceding siblings ...)
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 07/11] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 09/11] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
                         ` (3 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 ffbuild/common.mak           |  28 ++-
 fftools/Makefile             |   3 +-
 fftools/resources/.gitignore |   4 +
 fftools/resources/Makefile   |  27 +++
 fftools/resources/graph.css  | 353 +++++++++++++++++++++++++++++++++++
 fftools/resources/graph.html |  86 +++++++++
 fftools/resources/resman.c   | 213 +++++++++++++++++++++
 fftools/resources/resman.h   |  50 +++++
 8 files changed, 762 insertions(+), 2 deletions(-)
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h

diff --git a/ffbuild/common.mak b/ffbuild/common.mak
index ca45a0f368..6717092d44 100644
--- a/ffbuild/common.mak
+++ b/ffbuild/common.mak
@@ -139,6 +139,32 @@ else
 	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
 endif
 
+# 1) Preprocess CSS to a minified version
+%.css.min: %.css
+	# Must start with a tab in the real Makefile
+	sed 's!/\\*.*\\*/!!g' $< \
+	| tr '\n' ' ' \
+	| tr -s ' ' \
+	| sed 's/^ //; s/ $$//' \
+	> $@
+
+# 2) Gzip the minified CSS
+%.css.min.gz: %.css.min
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) >$@
+
+# 3) Convert the gzipped CSS to a .c array
+%.css.c: %.css.min.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
+# 4) Gzip the HTML file (no minification needed)
+%.html.gz: TAG = GZIP
+%.html.gz: %.html
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) > $@
+
+# 5) Convert the gzipped HTML to a .c array
+%.html.c: %.html.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
 clean::
 	$(RM) $(BIN2CEXE) $(CLEANSUFFIXES:%=ffbuild/%)
 
@@ -214,7 +240,7 @@ $(TOOLOBJS): | tools
 
 OUTDIRS := $(OUTDIRS) $(dir $(OBJS) $(HOBJS) $(HOSTOBJS) $(SLIBOBJS) $(SHLIBOBJS) $(STLIBOBJS) $(TESTOBJS))
 
-CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
+CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *.html.gz *.html.c *.css.gz *.css.c  *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
 LIBSUFFIXES       = *.a *.lib *.so *.so.* *.dylib *.dll *.def *.dll.a
 
 define RULES
diff --git a/fftools/Makefile b/fftools/Makefile
index e9c9891c34..a30bec889e 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -42,7 +42,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -56,6 +56,7 @@ all: $(AVPROGS)
 fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
+OUTDIRS += fftools/resources
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/resources/.gitignore b/fftools/resources/.gitignore
new file mode 100644
index 0000000000..5f496535a6
--- /dev/null
+++ b/fftools/resources/.gitignore
@@ -0,0 +1,4 @@
+*.html.c
+*.css.c
+*.html.gz
+*.css.gz
diff --git a/fftools/resources/Makefile b/fftools/resources/Makefile
new file mode 100644
index 0000000000..f3a0d0a970
--- /dev/null
+++ b/fftools/resources/Makefile
@@ -0,0 +1,27 @@
+clean::
+	$(RM) $(CLEANSUFFIXES:%=fftools/resources/%)
+
+
+HTML_RESOURCES := fftools/resources/graph.html \
+
+# .html => (gzip) .html.gz => (bin2c) .html.c => (cc) .o
+HTML_RESOURCES_GZ := $(HTML_RESOURCES:.html=.html.gz)
+HTML_RESOURCES_C := $(HTML_RESOURCES_GZ:.html.gz=.html.c)
+HTML_RESOURCES_OBJS := $(HTML_RESOURCES_C:.c=.o)
+
+CSS_RESOURCES := fftools/resources/graph.css   \
+
+# .css => (sh) .css.min  => (gzip) .css.min.gz => (bin2c) .css.c => (cc) .o
+CSS_RESOURCES_MIN := $(CSS_RESOURCES:.css=.css.min)
+CSS_RESOURCES_GZ := $(CSS_RESOURCES_MIN:.css.min=.css.min.gz)
+CSS_RESOURCES_C := $(CSS_RESOURCES_GZ:.css.min.gz=.css.c)
+CSS_RESOURCES_OBJS := $(CSS_RESOURCES_C:.c=.o)
+
+# Uncomment to prevent deletion
+#.PRECIOUS: %.css.c %.css.min %.css.gz  %.css.min.gz
+
+OBJS-resman +=                  \
+    fftools/resources/resman.o          \
+    $(HTML_RESOURCES_OBJS)      \
+    $(CSS_RESOURCES_OBJS)       \
+
diff --git a/fftools/resources/graph.css b/fftools/resources/graph.css
new file mode 100644
index 0000000000..ab480673ab
--- /dev/null
+++ b/fftools/resources/graph.css
@@ -0,0 +1,353 @@
+/* Variables */
+.root {
+    --ff-colvideo: #6eaa7b;
+    --ff-colaudio: #477fb3;
+    --ff-colsubtitle: #ad76ab;
+    --ff-coltext: #666;
+}
+
+/* Common & Misc */
+.ff-inputfiles rect, .ff-outputfiles rect, .ff-inputstreams rect, .ff-outputstreams rect, .ff-decoders rect, .ff-encoders rect {
+    stroke-width: 0;
+    stroke: transparent;
+    filter: none !important;
+    fill: transparent !important;
+    display: none !important;
+}
+
+.cluster span {
+    color: var(--ff-coltext);
+}
+
+.cluster rect {
+    stroke: #dfdfdf !important;
+    transform: translateY(-2.3rem);
+    filter: drop-shadow(1px 2px 2px rgba(185,185,185,0.2)) !important;
+    rx: 8;
+    ry: 8;
+}
+
+.cluster-label {
+    font-size: 1.1rem;
+}
+
+    .cluster-label .nodeLabel {
+        display: block;
+        font-weight: 500;
+        color: var(--ff-coltext);
+    }
+
+    .cluster-label div {
+        max-width: unset !important;
+        padding: 3px;
+    }
+
+    .cluster-label foreignObject {
+        transform: translateY(-0.7rem);
+    }
+
+/* Input and output files */
+.node.ff-inputfile .label foreignObject, .node.ff-outputfile .label foreignObject {
+    overflow: visible;
+}
+
+.cluster.ff-inputfile .cluster-label foreignObject div:not(foreignObject div div), .cluster.ff-outputfile .cluster-label foreignObject div:not(foreignObject div div) {
+    display: table !important;
+}
+
+.nodeLabel div.ff-inputfile, .nodeLabel div.ff-outputfile {
+    font-size: 1.1rem;
+    font-weight: 500;
+    min-width: 14rem;
+    width: 100%;
+    display: flex;
+    color: var(--ff-coltext);
+    margin-top: 0.1rem;
+    line-height: 1.35;
+    padding-bottom: 1.9rem;
+}
+
+.nodeLabel div.ff-outputfile {
+    flex-direction: row-reverse;
+}
+
+.ff-inputfile .index, .ff-outputfile .index {
+    order: 2;
+    color: var(--ff-coltext);
+    text-align: center;
+    border-radius: 0.45rem;
+    border: 0.18em solid #666666db;
+    font-weight: 600;
+    padding: 0 0.3em;
+    opacity: 0.8;
+}
+
+    .ff-inputfile .index::before {
+        content: 'In ';
+    }
+
+    .ff-outputfile .index::before {
+        content: 'Out ';
+    }
+
+.ff-inputfile .demuxer_name, .ff-outputfile .muxer_name {
+    flex: 1;
+    order: 1;
+    font-size: 0.9rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: center;
+    max-width: 8rem;
+    align-content: center;
+    margin: 0.2rem 0.4rem 0 0.4rem;
+}
+
+.ff-inputfile .file_extension, .ff-outputfile .file_extension {
+    order: 0;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.45rem;
+    font-weight: 600;
+    padding: 0 0.4em;
+    align-content: center;
+    opacity: 0.8;
+}
+
+.ff-inputfile .url, .ff-outputfile .url {
+    order: 4;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0.75rem;
+    font-size: 0.7rem;
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin: 0 0.3rem;
+    direction: rtl;
+    color: #999;
+}
+
+.cluster.ff-inputfile rect, .cluster.ff-outputfile rect {
+    transform: translateY(-1.8rem);
+    fill: url(#ff-radgradient);
+}
+
+/* Input and output streams */
+.node.ff-inputstream rect, .node.ff-outputstream rect {
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    fill: white;
+    stroke: #e5e5e5 !important;
+    height: 2.7rem;
+    transform: translateY(0.2rem);
+    filter: none;
+    rx: 3;
+    ry: 3;
+}
+
+.node.ff-inputstream .label foreignObject, .node.ff-outputstream .label foreignObject {
+    transform: translateY(-0.2%);
+    overflow: visible;
+}
+
+    .node.ff-inputstream .label foreignObject div:not(foreignObject div div), .node.ff-outputstream .label foreignObject div:not(foreignObject div div) {
+        display: block !important;
+        line-height: 1.5 !important;
+    }
+
+.nodeLabel div.ff-inputstream, .nodeLabel div.ff-outputstream {
+    font-size: 1.0rem;
+    font-weight: 500;
+    min-width: 12rem;
+    width: 100%;
+    display: flex;
+}
+
+.nodeLabel div.ff-outputstream {
+    flex-direction: row-reverse;
+}
+
+.ff-inputstream .name, .ff-outputstream .name {
+    flex: 1;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: left;
+    align-content: center;
+    margin-bottom: -0.15rem;
+}
+
+.ff-inputstream .index, .ff-outputstream .index {
+    flex: 0 0 1.4rem;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.3rem;
+    font-weight: 600;
+    margin-right: -0.3rem;
+    margin-left: 0.4rem;
+    opacity: 0.8;
+}
+
+.ff-outputstream .index {
+    margin-right: 0.6rem;
+    margin-left: -0.4rem;
+}
+
+.ff-inputstream::before, .ff-outputstream::before {
+    font-variant-emoji: text;
+    flex: 0 0 2rem;
+    margin-left: -0.8rem;
+    margin-right: 0.2rem;
+}
+
+.ff-outputstream::before {
+    margin-left: 0.2rem;
+    margin-right: -0.6rem;
+}
+
+.ff-inputstream.video::before, .ff-outputstream.video::before {
+    content: '\239A';
+    color: var(--ff-colvideo);
+    font-size: 2.25rem;
+    line-height: 0.5;
+    font-weight: bold;
+}
+
+.ff-inputstream.audio::before, .ff-outputstream.audio::before {
+    content: '\1F39D';
+    color: var(--ff-colaudio);
+    font-size: 1.75rem;
+    line-height: 0.9;
+}
+
+.ff-inputstream.subtitle::before, .ff-outputstream.subtitle::before {
+    content: '\1AC';
+    color: var(--ff-colsubtitle);
+    font-size: 1.2rem;
+    line-height: 1.1;
+    transform: scaleX(1.5);
+    margin-top: 0.050rem;
+}
+
+.ff-inputstream.attachment::before, .ff-outputstream.attachment::before {
+    content: '\1F4CE';
+    font-size: 1.3rem;
+    line-height: 1.15;
+}
+
+.ff-inputstream.data::before, .ff-outputstream.data::before {
+    content: '\27E8\2219\2219\2219\27E9';
+    font-size: 1.15rem;
+    line-height: 1.17;
+    letter-spacing: -0.3px;
+}
+
+/* Filter Graphs */
+.cluster.ff-filters rect {
+    stroke-dasharray: 6 !important;
+    stroke-width: 1.3px;
+    stroke: #d1d1d1 !important;
+    filter: none !important;
+}
+
+.cluster.ff-filters div.ff-filters .id {
+    display: none;
+}
+
+.cluster.ff-filters div.ff-filters .name {
+    margin-right: 0.5rem;
+    font-size: 0.9rem;
+}
+
+.cluster.ff-filters div.ff-filters .description {
+    font-weight: 400;
+    font-size: 0.75rem;
+    vertical-align: middle;
+    color: #777;
+    font-family: Cascadia Code, Lucida Console, monospace;
+}
+
+/* Filter Shapes */
+.node.ff-filter rect {
+    rx: 10;
+    ry: 10;
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.node.ff-filter .label foreignObject {
+    transform: translateY(-0.4rem);
+    overflow: visible;
+}
+
+.nodeLabel div.ff-filter {
+    font-size: 1.0rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    min-width: 5.5rem;
+    margin-bottom: 0.5rem;
+}
+
+    .nodeLabel div.ff-filter span {
+        color: inherit;
+    }
+
+/* Decoders & Encoders */
+.node.ff-decoder rect, .node.ff-encoder rect {
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.nodeLabel div.ff-decoder, .nodeLabel div.ff-encoder {
+    font-size: 0.85rem;
+    font-weight: 500;
+    min-width: 3.5rem;
+}
+
+/* Links and Arrows */
+path.flowchart-link[id|='video'] {
+    stroke: var(--ff-colvideo);
+}
+
+path.flowchart-link[id|='audio'] {
+    stroke: var(--ff-colaudio);
+}
+
+path.flowchart-link[id|='subtitle'] {
+    stroke: var(--ff-colsubtitle);
+}
+
+marker.marker path {
+    fill: context-stroke;
+}
+
+.edgeLabel foreignObject {
+    transform: translateY(-1rem);
+}
+
+.edgeLabel p {
+    background: transparent;
+    white-space: nowrap;
+    margin: 1rem 0.5rem !important;
+    font-weight: 500;
+    color: var(--ff-coltext);
+}
+
+.edgeLabel, .labelBkg {
+    background: transparent;
+}
+
+.edgeLabels .edgeLabel * {
+    font-size: 0.8rem;
+}
diff --git a/fftools/resources/graph.html b/fftools/resources/graph.html
new file mode 100644
index 0000000000..cd94276fd4
--- /dev/null
+++ b/fftools/resources/graph.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <title>FFmpeg Graph</title>
+</head>
+<body>
+<style>
+    html {
+        color: #666;
+        font-family: Roboto;
+        height: 100%;
+    }
+
+    body {
+        background-color: #f9f9f9;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        margin: 0;
+        padding: 1.7rem 1.7rem 3.5rem 1.7rem;
+    }
+
+    div#banner {
+        align-items: center;
+        display: flex;
+        flex-direction: row;
+        margin-bottom: 1.5rem;
+        margin-left: 0.6vw;
+    }
+
+    div#header {
+        aspect-ratio: 1/1;
+        background-image: url(https://trac.ffmpeg.org/ffmpeg-logo.png);
+        background-size: cover;
+        width: 1.6rem;
+    }
+
+    h1 {
+        font-size: 1.2rem;
+        margin: 0 0.5rem;
+    }
+
+    pre.mermaid {
+        align-items: center;
+        background-color: white;
+        box-shadow: 2px 2px 25px 0px #00000010;
+        color: transparent;
+        display: flex;
+        flex: 1;
+        justify-content: center;
+        margin: 0;
+        overflow: overlay;
+    }
+
+    pre.mermaid svg {
+        height: auto;
+        margin: 0;
+        max-width: unset !important;
+        width: auto;
+    }
+
+    pre.mermaid svg * {
+        user-select: none;
+    }
+</style>
+<div id="banner">
+    <div id="header"></div>
+    <h1>FFmpeg Execution Graph</h1>
+</div>
+<pre class="mermaid">
+__###__
+</pre>
+<script type="module">
+        import vanillaJsWheelZoom from 'https://cdn.jsdelivr.net/npm/vanilla-js-wheel-zoom@9.0.4/+esm';
+        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
+        function initViewer() {
+            var element = document.querySelector('.mermaid svg')
+            vanillaJsWheelZoom.create('pre.mermaid svg', { type: 'html', smoothTimeDrag: 0, width: element.clientWidth, height: element.clientHeight, maxScale: 3 });
+        }
+        mermaid.initialize({ startOnLoad: false }); 
+        document.fonts.ready.then(() => { mermaid.run({ querySelector: '.mermaid', postRenderCallback: initViewer }); });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/fftools/resources/resman.c b/fftools/resources/resman.c
new file mode 100644
index 0000000000..488aaeecf6
--- /dev/null
+++ b/fftools/resources/resman.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <zlib.h>
+#include "resman.h"
+#include <libavformat/url.h>
+#include "fftools/ffmpeg_filter.h"
+#include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+
+extern const unsigned char ff_graph_html_data[];
+extern const unsigned int ff_graph_html_len;
+
+extern const unsigned char ff_graph_css_data[];
+extern const unsigned ff_graph_css_len;
+
+static const FFResourceDefinition resource_definitions[] = {
+    [FF_RESOURCE_GRAPH_CSS]   = { FF_RESOURCE_GRAPH_CSS,   "graph.css",   &ff_graph_css_data[0],   &ff_graph_css_len   },
+    [FF_RESOURCE_GRAPH_HTML]  = { FF_RESOURCE_GRAPH_HTML,  "graph.html",  &ff_graph_html_data[0],  &ff_graph_html_len  },
+};
+
+
+static const AVClass resman_class = {
+    .class_name = "ResourceManager",
+};
+
+typedef struct ResourceManagerContext {
+    const AVClass *class;
+    AVDictionary *resource_dic;
+} ResourceManagerContext;
+
+static AVMutex mutex = AV_MUTEX_INITIALIZER;
+
+ResourceManagerContext *resman_ctx = NULL;
+
+
+static int decompress_gzip(ResourceManagerContext *ctx, uint8_t *in, unsigned in_len, char **out, size_t *out_len)
+{
+    z_stream strm;
+    unsigned chunk = 65534;
+    int ret;
+    uint8_t *buf;
+
+    *out = NULL;
+    memset(&strm, 0, sizeof(strm));
+
+    // Allocate output buffer with extra byte for null termination
+    buf = (uint8_t *)av_mallocz(chunk + 1);
+    if (!buf) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to allocate decompression buffer\n");
+        return AVERROR(ENOMEM);
+    }
+
+    // 15 + 16 tells zlib to detect GZIP or zlib automatically
+    ret = inflateInit2(&strm, 15 + 16);
+    if (ret != Z_OK) {
+        av_log(ctx, AV_LOG_ERROR, "Error during zlib initialization: %s\n", strm.msg);
+        av_free(buf);
+        return AVERROR(ENOSYS);
+    }
+
+    strm.avail_in  = in_len;
+    strm.next_in   = in;
+    strm.avail_out = chunk;
+    strm.next_out  = buf;
+
+    ret = inflate(&strm, Z_FINISH);
+    if (ret != Z_OK && ret != Z_STREAM_END) {
+        av_log(ctx, AV_LOG_ERROR, "Inflate failed: %d, %s\n", ret, strm.msg);
+        inflateEnd(&strm);
+        av_free(buf);
+        return (ret == Z_STREAM_END) ? Z_OK : ((ret == Z_OK) ? Z_BUF_ERROR : ret);
+    }
+
+    if (strm.avail_out == 0) {
+        // TODO: Error or loop decoding?
+        av_log(ctx, AV_LOG_WARNING, "Decompression buffer may be too small\n");
+    }
+
+    *out_len = chunk - strm.avail_out;
+    buf[*out_len] = 0; // Ensure null termination
+
+    inflateEnd(&strm);
+    *out = (char *)buf;
+    return Z_OK;
+}
+
+static ResourceManagerContext *get_resman_context(void)
+{
+    ResourceManagerContext *res = resman_ctx;
+
+    ff_mutex_lock(&mutex);
+
+    if (res)
+        goto end;
+
+    res = av_mallocz(sizeof(ResourceManagerContext));
+    if (!res) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to allocate resource manager context\n");
+        goto end;
+    }
+
+    res->class = &resman_class;
+    resman_ctx = res;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
+
+
+void ff_resman_uninit(void)
+{
+    ff_mutex_lock(&mutex);
+
+    if (resman_ctx) {
+        if (resman_ctx->resource_dic)
+            av_dict_free(&resman_ctx->resource_dic);
+        av_freep(&resman_ctx);
+    }
+
+    ff_mutex_unlock(&mutex);
+}
+
+
+char *ff_resman_get_string(FFResourceId resource_id)
+{
+    ResourceManagerContext *ctx               = get_resman_context();
+    FFResourceDefinition resource_definition = { 0 };
+    AVDictionaryEntry *dic_entry;
+    char *res = NULL;
+
+    if (!ctx)
+        return NULL;
+
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(resource_definitions); ++i) {
+        FFResourceDefinition def = resource_definitions[i];
+        if (def.resource_id == resource_id) {
+            resource_definition = def;
+            break;
+        }
+    }
+
+    if (!resource_definition.name) {
+        av_log(ctx, AV_LOG_ERROR, "Unable to find resource with ID %d\n", resource_id);
+        return NULL;
+    }
+
+    ff_mutex_lock(&mutex);
+
+    dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+    if (!dic_entry) {
+        char *out = NULL;
+        size_t out_len;
+        int dict_ret;
+
+        int ret = decompress_gzip(ctx, (uint8_t *)resource_definition.data, *resource_definition.data_len, &out, &out_len);
+
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to decompress the resource with ID %d\n", resource_id);
+            goto end;
+        }
+
+        dict_ret = av_dict_set(&ctx->resource_dic, resource_definition.name, out, 0);
+        if (dict_ret < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to store decompressed resource in dictionary: %d\n", dict_ret);
+            av_freep(&out);
+            goto end;
+        }
+
+        av_freep(&out);
+        dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+        if (!dic_entry) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to retrieve resource from dictionary after storing it\n");
+            goto end;
+        }
+    }
+
+    res = dic_entry->value;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
diff --git a/fftools/resources/resman.h b/fftools/resources/resman.h
new file mode 100644
index 0000000000..6485db5091
--- /dev/null
+++ b/fftools/resources/resman.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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_RESOURCES_RESMAN_H
+#define FFTOOLS_RESOURCES_RESMAN_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "fftools/ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+#include "fftools/textformat/avtextformat.h"
+
+typedef enum {
+    FF_RESOURCE_GRAPH_CSS,
+    FF_RESOURCE_GRAPH_HTML,
+} FFResourceId;
+
+typedef struct FFResourceDefinition {
+    FFResourceId resource_id;
+    const char *name;
+
+    const unsigned char *data;
+    const unsigned *data_len;
+
+} FFResourceDefinition;
+
+void ff_resman_uninit(void);
+
+char *ff_resman_get_string(FFResourceId resource_id);
+
+#endif /* FFTOOLS_RESOURCES_RESMAN_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 09/11] fftools/ffmpeg_mux: Make ms_from_ost() inline
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
                         ` (7 preceding siblings ...)
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 08/11] fftools/resources: Add resource manager files softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 10/11] fftools/graphprint: Add execution graph printing softworkz
                         ` (2 subsequent siblings)
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_mux.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fftools/ffmpeg_mux.h b/fftools/ffmpeg_mux.h
index f41f2c18fa..4ca8ab73a4 100644
--- a/fftools/ffmpeg_mux.h
+++ b/fftools/ffmpeg_mux.h
@@ -123,7 +123,7 @@ typedef struct Muxer {
 
 int mux_check_init(void *arg);
 
-static MuxStream *ms_from_ost(OutputStream *ost)
+static inline MuxStream *ms_from_ost(OutputStream *ost)
 {
     return (MuxStream*)ost;
 }
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 10/11] fftools/graphprint: Add execution graph printing
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
                         ` (8 preceding siblings ...)
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 09/11] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 11/11] fftools/graphprint: Now, make it a Killer-Feature! softworkz
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

The key benefits are:

- Different to other graph printing methods, this is outputting:
  - all graphs with runtime state
    (including auto-inserted filters)
  - each graph with its inputs and outputs
  - all filters with their in- and output pads
  - all connections between all input- and output pads
  - for each connection:
    - the runtime-negotiated format and media type
    - the hw context
    - if video hw context, both: hw pixfmt + sw pixfmt
- Output can either be printed to stdout or written to specified file
- Output is machine-readable
- Use the same output implementation as ffprobe, supporting multiple
  formats

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi                   |   10 +
 fftools/Makefile                  |   20 +-
 fftools/ffmpeg.c                  |    4 +
 fftools/ffmpeg.h                  |    3 +
 fftools/ffmpeg_filter.c           |    5 +
 fftools/ffmpeg_opt.c              |   13 +
 fftools/graph/graphprint.c        | 1102 +++++++++++++++++++++++++++++
 fftools/graph/graphprint.h        |   30 +
 fftools/textformat/avtextformat.c |    2 +
 fftools/textformat/avtextformat.h |   29 +
 fftools/textformat/tf_mermaid.c   |  658 +++++++++++++++++
 fftools/textformat/tf_mermaid.h   |   41 ++
 12 files changed, 1916 insertions(+), 1 deletion(-)
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 17ba876ea3..35675b5309 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1394,6 +1394,16 @@ It is on by default, to explicitly disable it you need to specify @code{-nostats
 @item -stats_period @var{time} (@emph{global})
 Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
 
+@item -print_graphs (@emph{global})
+Prints execution graph details to stderr in the format set via -print_graphs_format.
+
+@item -print_graphs_file @var{filename} (@emph{global})
+Writes execution graph details to the specified file in the format set via -print_graphs_format.
+
+@item -print_graphs_format @var{format} (@emph{global})
+Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
+The default format is json.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index a30bec889e..361a4fd574 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -9,6 +9,8 @@ AVBASENAMES  = ffmpeg ffplay ffprobe
 ALLAVPROGS   = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
 ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
 
+include $(SRC_PATH)/fftools/resources/Makefile
+
 OBJS-ffmpeg +=                  \
     fftools/ffmpeg_dec.o        \
     fftools/ffmpeg_demux.o      \
@@ -19,8 +21,21 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
+    fftools/graph/graphprint.o        \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
+    fftools/textformat/avtextformat.o \
+    fftools/textformat/tf_compact.o   \
+    fftools/textformat/tf_default.o   \
+    fftools/textformat/tf_flat.o      \
+    fftools/textformat/tf_ini.o       \
+    fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
+    fftools/textformat/tf_xml.o       \
+    fftools/textformat/tw_avio.o      \
+    fftools/textformat/tw_buffer.o    \
+    fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffprobe +=                       \
     fftools/textformat/avtextformat.o \
@@ -29,10 +44,12 @@ OBJS-ffprobe +=                       \
     fftools/textformat/tf_flat.o      \
     fftools/textformat/tf_ini.o       \
     fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
     fftools/textformat/tf_xml.o       \
     fftools/textformat/tw_avio.o      \
     fftools/textformat/tw_buffer.o    \
     fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffplay += fftools/ffplay_renderer.o
 
@@ -42,7 +59,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources fftools/graph
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -57,6 +74,7 @@ fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
 OUTDIRS += fftools/resources
+OUTDIRS += fftools/graph
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..6766ec209c 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -81,6 +81,7 @@
 #include "ffmpeg.h"
 #include "ffmpeg_sched.h"
 #include "ffmpeg_utils.h"
+#include "graph/graphprint.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
+    if (print_graphs || print_graphs_file)
+        print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
+
     if (do_benchmark) {
         int64_t maxrss = getmaxrss() / 1024;
         av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 5869979214..7fbf0ad532 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -717,6 +717,9 @@ extern float max_error_rate;
 extern char *filter_nbthreads;
 extern int filter_complex_nbthreads;
 extern int vstats_version;
+extern int print_graphs;
+extern char *print_graphs_file;
+extern char *print_graphs_format;
 extern int auto_conversion_filters;
 
 extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index eab9487f97..b774606562 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -22,6 +22,7 @@
 
 #include "ffmpeg.h"
 #include "ffmpeg_filter.h"
+#include "graph/graphprint.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -2983,6 +2984,10 @@ read_frames:
     }
 
 finish:
+
+    if (print_graphs || print_graphs_file)
+        print_filtergraph(fg, fgt.graph);
+
     // EOF is normal termination
     if (ret == AVERROR_EOF)
         ret = 0;
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 6ec325f51e..3d1efe32f9 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -47,6 +47,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/stereo3d.h"
+#include "graph/graphprint.h"
 
 HWDevice *filter_hw_device;
 
@@ -75,6 +76,9 @@ float max_error_rate  = 2.0/3;
 char *filter_nbthreads;
 int filter_complex_nbthreads = 0;
 int vstats_version = 2;
+int print_graphs = 0;
+char *print_graphs_file = NULL;
+char *print_graphs_format = NULL;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1735,6 +1739,15 @@ const OptionDef options[] = {
         { .func_arg = opt_filter_complex_script },
         "deprecated, use -/filter_complex instead", "filename" },
 #endif
+    { "print_graphs",   OPT_TYPE_BOOL, 0,
+        { &print_graphs },
+        "print execution graph data to stderr" },
+    { "print_graphs_file", OPT_TYPE_STRING, 0,
+        { &print_graphs_file },
+        "write execution graph data to the specified file", "filename" },
+    { "print_graphs_format", OPT_TYPE_STRING, 0,
+        { &print_graphs_format },
+      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
new file mode 100644
index 0000000000..89c38d2e36
--- /dev/null
+++ b/fftools/graph/graphprint.c
@@ -0,0 +1,1102 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include <string.h>
+#include <stdatomic.h>
+
+#include "graphprint.h"
+
+#include <libavformat/url.h>
+
+#include "fftools/ffmpeg_filter.h"
+#include "fftools/ffmpeg_mux.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+#include "fftools/textformat/avtextformat.h"
+#include "fftools/textformat/tf_mermaid.h"
+#include "fftools/resources/resman.h"
+
+typedef enum {
+    SECTION_ID_ROOT,
+    SECTION_ID_FILTERGRAPHS,
+    SECTION_ID_FILTERGRAPH,
+    SECTION_ID_GRAPH_INPUTS,
+    SECTION_ID_GRAPH_INPUT,
+    SECTION_ID_GRAPH_OUTPUTS,
+    SECTION_ID_GRAPH_OUTPUT,
+    SECTION_ID_FILTERS,
+    SECTION_ID_FILTER,
+    SECTION_ID_FILTER_INPUTS,
+    SECTION_ID_FILTER_INPUT,
+    SECTION_ID_FILTER_OUTPUTS,
+    SECTION_ID_FILTER_OUTPUT,
+    SECTION_ID_HWFRAMESCONTEXT,
+    SECTION_ID_INPUTFILES,
+    SECTION_ID_INPUTFILE,
+    SECTION_ID_INPUTSTREAMS,
+    SECTION_ID_INPUTSTREAM,
+    SECTION_ID_OUTPUTFILES,
+    SECTION_ID_OUTPUTFILE,
+    SECTION_ID_OUTPUTSTREAMS,
+    SECTION_ID_OUTPUTSTREAM,
+    SECTION_ID_STREAMLINKS,
+    SECTION_ID_STREAMLINK,
+    SECTION_ID_DECODERS,
+    SECTION_ID_DECODER,
+    SECTION_ID_ENCODERS,
+    SECTION_ID_ENCODER,
+} SectionID;
+
+static struct AVTextFormatSection sections[] = {
+    [SECTION_ID_ROOT]            = { SECTION_ID_ROOT, "root", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER, { SECTION_ID_FILTERGRAPHS, SECTION_ID_INPUTFILES, SECTION_ID_OUTPUTFILES, SECTION_ID_DECODERS, SECTION_ID_ENCODERS, SECTION_ID_STREAMLINKS, -1 } },
+
+    [SECTION_ID_FILTERGRAPHS]    = { SECTION_ID_FILTERGRAPHS, "graphs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
+    [SECTION_ID_FILTERGRAPH]     = { SECTION_ID_FILTERGRAPH, "graph", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { SECTION_ID_GRAPH_INPUTS, SECTION_ID_GRAPH_OUTPUTS, SECTION_ID_FILTERS, -1 }, .element_name = "graph_info" },
+
+    [SECTION_ID_GRAPH_INPUTS]    = { SECTION_ID_GRAPH_INPUTS, "graph_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_INPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_INPUT]     = { SECTION_ID_GRAPH_INPUT, "graph_input", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_GRAPH_OUTPUTS]   = { SECTION_ID_GRAPH_OUTPUTS, "graph_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_OUTPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_OUTPUT]    = { SECTION_ID_GRAPH_OUTPUT, "graph_output", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTERS]         = { SECTION_ID_FILTERS, "filters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_FILTER, -1 }, .id_key = "graph_id" },
+    [SECTION_ID_FILTER]          = { SECTION_ID_FILTER, "filter", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { SECTION_ID_FILTER_INPUTS, SECTION_ID_FILTER_OUTPUTS, -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_INPUTS]   = { SECTION_ID_FILTER_INPUTS, "filter_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_INPUT, -1 } },
+    [SECTION_ID_FILTER_INPUT]    = { SECTION_ID_FILTER_INPUT, "filter_input", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "source_filter_id", .dest_id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_OUTPUTS]  = { SECTION_ID_FILTER_OUTPUTS, "filter_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_OUTPUT, -1 } },
+    [SECTION_ID_FILTER_OUTPUT]   = { SECTION_ID_FILTER_OUTPUT, "filter_output", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "filter_id", .dest_id_key = "dest_filter_id" },
+
+    [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context",  0, { -1 }, },
+
+    [SECTION_ID_INPUTFILES]      = { SECTION_ID_INPUTFILES, "inputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTFILE]       = { SECTION_ID_INPUTFILE, "inputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_INPUTSTREAMS]    = { SECTION_ID_INPUTSTREAMS, "inputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTSTREAM]     = { SECTION_ID_INPUTSTREAM, "inputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTFILES]     = { SECTION_ID_OUTPUTFILES, "outputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTFILE]      = { SECTION_ID_OUTPUTFILE, "outputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTSTREAMS]   = { SECTION_ID_OUTPUTSTREAMS, "outputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTSTREAM]    = { SECTION_ID_OUTPUTSTREAM, "outputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id", },
+
+    [SECTION_ID_STREAMLINKS]     = { SECTION_ID_STREAMLINKS, "streamlinks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAMLINK, -1 } },
+    [SECTION_ID_STREAMLINK]      = { SECTION_ID_STREAMLINK, "streamlink", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .src_id_key = "source_stream_id", .dest_id_key = "dest_stream_id" },
+
+    [SECTION_ID_DECODERS]        = { SECTION_ID_DECODERS, "decoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_DECODER, -1 } },
+    [SECTION_ID_DECODER]         = { SECTION_ID_DECODER, "decoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "source_id", .dest_id_key = "id" },
+
+    [SECTION_ID_ENCODERS]        = { SECTION_ID_ENCODERS, "encoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_ENCODER, -1 } },
+    [SECTION_ID_ENCODER]         = { SECTION_ID_ENCODER, "encoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "id", .dest_id_key = "dest_id" },
+};
+
+typedef struct GraphPrintContext {
+    AVTextFormatContext *tfc;
+    AVTextWriterContext *wctx;
+    AVDiagramConfig diagram_config;
+
+    int id_prefix_num;
+    int is_diagram;
+    int opt_flags;
+    int skip_buffer_filters;
+    AVBPrint pbuf;
+
+} GraphPrintContext;
+
+/* Text Format API Shortcuts */
+#define print_id(k, v)          print_sanizied_id(gpc, k, v, 0)
+#define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
+#define print_int(k, v)         avtext_print_integer(tfc, k, v)
+#define print_int_opt(k, v)     avtext_print_integer_flags(tfc, k, v, gpc->opt_flags)
+#define print_q(k, v, s)        avtext_print_rational(tfc, k, v, s)
+#define print_str(k, v)         avtext_print_string(tfc, k, v, 0)
+#define print_str_opt(k, v)     avtext_print_string(tfc, k, v, gpc->opt_flags)
+#define print_val(k, v, u)      avtext_print_unit_int(tfc, k, v, u)
+
+#define print_fmt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, 0);    \
+} while (0)
+
+#define print_fmt_opt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, gpc->opt_flags);    \
+} while (0)
+
+
+static atomic_int prefix_num = 0;
+
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    unsigned i;
+    for (i = 0; src[i] && i < dst_size - 1; i++)
+        dst[i]      = (char)av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static char *get_extension(const char *url)
+{
+    const char *ext;
+    URLComponents uc;
+    int ret;
+    char scratchpad[128];
+
+    if (!url)
+        return 0;
+
+    ret = ff_url_decompose(&uc, url, NULL);
+    if (ret < 0)
+        return NULL;
+    for (ext = uc.query; *ext != '.' && ext > uc.path; ext--) {
+    }
+
+    if (*ext != '.')
+        return 0;
+    if (uc.query - ext > sizeof(scratchpad))
+        return NULL; //not enough memory in our scratchpad
+    av_strlcpy(scratchpad, ext + 1, uc.query - ext);
+
+    return av_strdup(scratchpad);
+}
+
+static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+
+    if (!hw_device_context)
+        return;
+
+    print_int_opt("has_hw_device_context", 1);
+    print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
+}
+
+static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    const AVPixFmtDescriptor *pix_desc_hw;
+    const AVPixFmtDescriptor *pix_desc_sw;
+
+    if (!hw_frames_context || !hw_frames_context->device_ctx)
+        return;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT);
+
+    print_int_opt("has_hw_frames_context", 1);
+    print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
+
+    pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
+    if (pix_desc_hw) {
+        print_str("hw_pixel_format", pix_desc_hw->name);
+        if (pix_desc_hw->alias)
+            print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
+    }
+
+    pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
+    if (pix_desc_sw) {
+        print_str("sw_pixel_format", pix_desc_sw->name);
+        if (pix_desc_sw->alias)
+            print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
+    }
+
+    print_int_opt("width", hw_frames_context->width);
+    print_int_opt("height", hw_frames_context->height);
+    print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBufferRef *hw_frames_ctx;
+    char layout_string[64];
+
+    if (!link)
+        return;
+
+    hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
+
+    print_str_opt("media_type", av_get_media_type_string(link->type));
+
+    switch (link->type) {
+    case AVMEDIA_TYPE_VIDEO:
+
+        if (hw_frames_ctx && hw_frames_ctx->data) {
+            AVHWFramesContext *      hwfctx      = (AVHWFramesContext *)hw_frames_ctx->data;
+            const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
+            const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
+            if (pix_desc_hw && pix_desc_sw)
+                print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
+        } else {
+            print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
+        }
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        print_q("sar", link->sample_aspect_ratio, ':');
+
+        if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
+            print_str_opt("color_range", av_color_range_name(link->color_range));
+
+        if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
+            print_str("color_space", av_color_space_name(link->colorspace));
+        break;
+
+    case AVMEDIA_TYPE_SUBTITLE:
+        ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        break;
+
+    case AVMEDIA_TYPE_AUDIO:
+        av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
+        print_str("channel_layout", layout_string);
+        print_val("channels", link->ch_layout.nb_channels, "ch");
+        if (tfc->show_value_unit)
+            print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
+        else
+            print_val("sample_rate", link->sample_rate, "Hz");
+
+        break;
+    }
+
+    print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
+
+    if (hw_frames_ctx && hw_frames_ctx->data)
+        print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
+}
+
+static char sanitize_char(const char c)
+{
+    if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+        return c;
+    return '_';
+}
+
+static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBPrint buf;
+
+    if (!key || !id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    print_str(key, buf.str);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+    AVBPrint buf;
+
+    if (!id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    sec_ctx.context_id = buf.str;
+
+    avtext_print_section_header(tfc, &sec_ctx, section_id);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static const char *get_filterpad_name(const AVFilterPad *pad)
+{
+    return pad ? avfilter_pad_get_name(pad, 0) : "pad";
+}
+
+static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
+
+    ////print_id("filter_id", filter->name);
+
+    if (filter->filter) {
+        print_str("filter_name", filter->filter->name);
+        print_str_opt("description", filter->filter->description);
+        print_int_opt("nb_inputs", filter->nb_inputs);
+        print_int_opt("nb_outputs", filter->nb_outputs);
+    }
+
+    if (filter->hw_device_ctx) {
+        AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
+        print_hwdevicecontext(gpc, device_context);
+        if (filter->extra_hw_frames > 0)
+            print_int("extra_hw_frames", filter->extra_hw_frames);
+    }
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
+
+    for (unsigned i = 0; i < filter->nb_inputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->inputs[i];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
+        sec_ctx.context_type = NULL;
+
+        print_int_opt("input_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
+
+        dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
+        if (dic_entry) {
+            char buf[256];
+            (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
+            print_id_noprefix("source_filter_id", buf);
+        } else {
+            print_id("source_filter_id", link->src->name);
+        }
+
+        print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
+
+    for (unsigned i = 0; i < filter->nb_outputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->outputs[i];
+        char buf[256];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
+        sec_ctx.context_type = NULL;
+
+        dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
+        if (dic_entry) {
+            (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
+            print_id_noprefix("dest_filter_id", buf);
+        } else {
+            print_id("dest_filter_id", link->dst->name);
+        }
+
+        print_int_opt("output_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->srcpad));
+        ////print_id("dest_filter_id", link->dst->name);
+        print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER
+}
+
+static void init_sections(void)
+{
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
+        sections[i].show_all_entries = 1;
+}
+
+static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVDictionary *input_map = NULL;
+    AVDictionary *output_map = NULL;
+
+    print_int("graph_index", fg->index);
+    print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+    print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
+    print_str("description", fgp->graph_desc);
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
+
+    for (int i = 0; i < fg->nb_inputs; i++) {
+        InputFilterPriv *ifilter = ifp_from_ifilter(fg->inputs[i]);
+        enum AVMediaType media_type = ifilter->type;
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
+
+        print_int("input_index", ifilter->index);
+
+        if (ifilter->linklabel)
+            print_str("link_label", (const char*)ifilter->linklabel);
+
+        if (ifilter->filter) {
+            print_id("filter_id", ifilter->filter->name);
+            print_str("filter_name", ifilter->filter->filter->name);
+        }
+
+        if (ifilter->linklabel && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
+        else if (ifilter->opts.name && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->opts.name, 0);
+
+        print_str("media_type", av_get_media_type_string(media_type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
+
+    for (int i = 0; i < fg->nb_outputs; i++) {
+        OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
+
+        print_int("output_index", ofilter->index);
+
+        print_str("name", ofilter->name);
+
+        if (fg->outputs[i]->linklabel)
+            print_str("link_label", (const char*)fg->outputs[i]->linklabel);
+
+        if (ofilter->filter) {
+            print_id("filter_id", ofilter->filter->name);
+            print_str("filter_name", ofilter->filter->filter->name);
+        }
+
+        if (ofilter->name && ofilter->filter)
+            av_dict_set(&output_map, ofilter->filter->name, ofilter->name, 0);
+
+
+        print_str("media_type", av_get_media_type_string(fg->outputs[i]->type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
+
+    if (graph) {
+        AVTextFormatSectionContext sec_ctx = { 0 };
+
+        sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTERS);
+
+        if (gpc->is_diagram) {
+            print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+            print_str("description", fgp->graph_desc);
+            print_str("id", sec_ctx.context_id);
+        }
+
+        av_freep(&sec_ctx.context_id);
+
+        for (unsigned i = 0; i < graph->nb_filters; i++) {
+            AVFilterContext *filter = graph->filters[i];
+
+            if (gpc->skip_buffer_filters) {
+                if (av_dict_get(input_map, filter->name, NULL, 0))
+                    continue;
+                if (av_dict_get(output_map, filter->name, NULL, 0))
+                    continue;
+            }
+
+            sec_ctx.context_id = filter->name;
+
+            print_filter(gpc, filter, input_map, output_map);
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
+    }
+
+    // Clean up dictionaries
+    av_dict_free(&input_map);
+    av_dict_free(&output_map);
+}
+
+static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    AVTextFormatContext       *tfc = gpc->tfc;
+    AVBPrint                   buf;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    sec_ctx.context_id = "Inputs";
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+    print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
+
+    for (int n = nb_ifiles - 1; n >= 0; n--) {
+        InputFile *ifi = ifiles[n];
+        AVFormatContext *fc = ifi->ctx;
+
+        sec_ctx.context_id = av_asprintf("Input_%d", n);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
+        av_freep(&sec_ctx.context_id);
+
+        print_fmt("index", "%d", ifi->index);
+
+        if (fc) {
+            print_str("demuxer_name", fc->iformat->name);
+            if (fc->url) {
+                char *extension = get_extension(fc->url);
+                if (extension) {
+                    print_str("file_extension", extension);
+                    av_freep(&extension);
+                }
+                print_str("url", fc->url);
+            }
+        }
+
+        sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
+
+        av_freep(&sec_ctx.context_id);
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+            const AVCodecDescriptor *codec_desc;
+
+            if (!ist || !ist->par)
+                continue;
+
+            codec_desc = avcodec_descriptor_get(ist->par->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
+
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_in_%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else if (ist->dec) {
+                char char_buf[256];
+                av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
+                av_bprintf(&buf, "%s", "Attachment");
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
+                av_bprintf(&buf, "%s", "Data");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ist->index);
+
+            if (ist->dec)
+                print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
+
+
+    print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
+
+    for (int n = 0; n < nb_ifiles; n++) {
+        InputFile *ifi = ifiles[n];
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+
+            if (!ist->decoder)
+                continue;
+
+            sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_DECODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("source_id", "r_in_%d_%d", n, i);
+            print_fmt("id", "in_%d_%d", n, i);
+
+            ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            print_fmt("name", "%s", ist->dec->name);
+
+            print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_DECODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            ////const AVCodecDescriptor *codec_desc;
+
+            if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
+                continue;
+
+            ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_ENCODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "out__%d_%d", n, i);
+            print_fmt("dest_id", "r_out__%d_%d", n, i);
+
+            print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
+
+    for (int n = nb_ofiles - 1; n >= 0; n--) {
+        OutputFile *of = ofiles[n];
+        Muxer *muxer = (Muxer *)of;
+
+        if (!muxer->fc)
+            continue;
+
+        sec_ctx.context_id = av_asprintf("Output_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
+
+        av_freep(&sec_ctx.context_id);
+
+        ////print_str_opt("index", av_get_media_type_string(of->index));
+        print_fmt("index", "%d", of->index);
+        ////print_str("url", of->url);
+        print_str("muxer_name", muxer->fc->oformat->name);
+        if (of->url) {
+            char *extension = get_extension(of->url);
+            if (extension) {
+                print_str("file_extension", extension);
+                av_freep(&extension);
+            }
+            print_str("url", of->url);
+        }
+
+        sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_out__%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else {
+                av_bprintf(&buf, "%s", "unknown");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ost->index);
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
+
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->ist && !ost->filter) {
+                sec_ctx.context_type = av_get_media_type_string(ost->type);
+                avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_STREAMLINK);
+                sec_ctx.context_type = NULL;
+
+                if (ost->enc) {
+                    print_fmt("dest_stream_id", "out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Transcode");
+                } else {
+                    print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Stream Copy");
+                }
+
+                print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+                avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
+
+    return 0;
+}
+
+
+static void uninit_graphprint(GraphPrintContext *gpc)
+{
+    if (gpc->tfc)
+        avtext_context_close(&gpc->tfc);
+
+    if (gpc->wctx)
+        avtextwriter_context_close(&gpc->wctx);
+
+    // Finalize the print buffer if it was initialized
+    av_bprint_finalize(&gpc->pbuf, NULL);
+}
+
+static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
+{
+    const AVTextFormatter *text_formatter;
+    AVTextFormatContext *tfc = NULL;
+    AVTextWriterContext *wctx = NULL;
+    GraphPrintContext *gpc = NULL;
+    char *w_args = NULL;
+    char *w_name;
+    int ret;
+
+    init_sections();
+    *pgpc = NULL;
+
+    av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("json");
+    if (!print_graphs_format) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &w_args);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    text_formatter = avtext_get_formatter_by_name(w_name);
+    if (!text_formatter) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtextwriter_create_buffer(&wctx, target_buf);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), 0, 0, 0, 0, -1, NULL);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    gpc = av_mallocz(sizeof(GraphPrintContext));
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    gpc->wctx = wctx;
+    gpc->tfc = tfc;
+    av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
+    gpc->is_diagram = !!(tfc->formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
+    if (gpc->is_diagram) {
+        tfc->show_value_unit = 1;
+        tfc->show_optional_fields = -1;
+        gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+        gpc->skip_buffer_filters = 1;
+        ////} else {
+        ////    gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+    }
+
+    if (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml")) {
+        gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
+
+        if (!strcmp(text_formatter->name, "mermaidhtml"))
+            gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
+
+        av_diagram_init(tfc, &gpc->diagram_config);
+    }
+
+    *pgpc = gpc;
+
+    return 0;
+
+fail:
+    if (tfc)
+        avtext_context_close(&tfc);
+    if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
+        avtextwriter_context_close(&wctx);
+    av_freep(&gpc);
+
+    return ret;
+}
+
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVBPrint *target_buf = &fgp->graph_print_buf;
+    int ret;
+
+    if (!fg || !fgp) {
+        av_log(NULL, AV_LOG_ERROR, "Invalid filter graph provided\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (target_buf->len)
+        av_bprint_finalize(target_buf, NULL);
+
+    ret = init_graphprint(&gpc, target_buf);
+    if (ret)
+        return ret;
+
+    if (!gpc) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to initialize graph print context\n");
+        return AVERROR(ENOMEM);
+    }
+
+    tfc = gpc->tfc;
+
+    // Due to the threading model each graph needs to print itself into a buffer
+    // from its own thread. The actual printing happens short before cleanup in ffmpeg.c
+    // where all graphs are assembled together. To make this work, we need to put the
+    // formatting context into the same state like it would be when printing all at once,
+    // so here we print the section headers and clear the buffer to get into the right state.
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+
+    av_bprint_clear(target_buf);
+
+    print_filtergraph_single(gpc, fg, graph);
+
+    if (gpc->is_diagram) {
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+    }
+
+    uninit_graphprint(gpc);
+
+    return 0;
+}
+
+static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    AVBPrint target_buf;
+    int ret;
+
+    ret = init_graphprint(&gpc, &target_buf);
+    if (ret)
+        goto cleanup;
+
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto cleanup;
+    }
+
+    tfc = gpc->tfc;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+
+    for (int i = 0; i < nb_graphs; i++) {
+        FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
+        AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+        if (graph_buf->len > 0) {
+            avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+            av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+            av_bprint_finalize(graph_buf, NULL);
+            avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        }
+    }
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->fg_simple) {
+                FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
+                AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+                if (graph_buf->len > 0) {
+                    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+                    av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+                    av_bprint_finalize(graph_buf, NULL);
+                    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+                }
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+
+    print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ROOT
+
+    if (print_graphs_file) {
+        AVIOContext *avio = NULL;
+
+        if (!strcmp(print_graphs_file, "-")) {
+            printf("%s", target_buf.str);
+        } else {
+            ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
+                goto cleanup;
+            }
+
+            avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
+            avio_flush(avio);
+
+            if ((ret = avio_closep(&avio)) < 0)
+                av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
+        }
+    }
+
+    if (print_graphs)
+        av_log(NULL, AV_LOG_INFO, "%s    %c", target_buf.str, '\n');
+
+cleanup:
+    // Properly clean up resources
+    if (gpc)
+        uninit_graphprint(gpc);
+
+    // Ensure the target buffer is properly finalized
+    av_bprint_finalize(&target_buf, NULL);
+
+    return ret;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+}
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
new file mode 100644
index 0000000000..9f043cc273
--- /dev/null
+++ b/fftools/graph/graphprint.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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_GRAPH_GRAPHPRINT_H
+#define FFTOOLS_GRAPH_GRAPHPRINT_H
+
+#include "fftools/ffmpeg.h"
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles);
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index d2d84c4f1d..0692dbc59d 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -709,6 +709,8 @@ static void formatters_register_all(void)
     registered_formatters[4] = &avtextformatter_ini;
     registered_formatters[5] = &avtextformatter_json;
     registered_formatters[6] = &avtextformatter_xml;
+    registered_formatters[7] = &avtextformatter_mermaid;
+    registered_formatters[8] = &avtextformatter_mermaidhtml;
 }
 
 const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index 16cd9b214f..4890cb846d 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -31,6 +31,12 @@
 
 #define SECTION_MAX_NB_CHILDREN 11
 
+typedef struct AVTextFormatSectionContext {
+    char *context_id;
+    const char *context_type;
+    int context_flags;
+} AVTextFormatSectionContext;
+
 
 typedef struct AVTextFormatSection {
     int id;             ///< unique id identifying a section
@@ -42,6 +48,10 @@ typedef struct AVTextFormatSection {
                                            ///  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
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE       32 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS      64 ///< ...
+#define AV_TEXTFORMAT_SECTION_PRINT_TAGS         128 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH   256 ///< ...
 
     int flags;
     const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
@@ -50,12 +60,17 @@ typedef struct AVTextFormatSection {
     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;
+    const char *id_key;          ///< name of the key to be used as the id 
+    const char *src_id_key;     ///< name of the key to be used as the source id for diagram connections
+    const char *dest_id_key;   ///< name of the key to be used as the target id for diagram connections
+    const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
 } AVTextFormatSection;
 
 typedef struct AVTextFormatContext AVTextFormatContext;
 
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
+#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER         4
 
 typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
@@ -64,6 +79,18 @@ typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_NB
 } StringValidation;
 
+typedef enum {
+    AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_NONDIR,
+    AV_TEXTFORMAT_LINKTYPE_HIDDEN,
+    AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
+} AVTextFormatLinkType;
+
 typedef struct AVTextFormatter {
     const AVClass *priv_class;      ///< private class of the formatter, if any
     int priv_size;                  ///< private size for the formatter context
@@ -167,5 +194,7 @@ extern const AVTextFormatter avtextformatter_flat;
 extern const AVTextFormatter avtextformatter_ini;
 extern const AVTextFormatter avtextformatter_json;
 extern const AVTextFormatter avtextformatter_xml;
+extern const AVTextFormatter avtextformatter_mermaid;
+extern const AVTextFormatter avtextformatter_mermaidhtml;
 
 #endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
diff --git a/fftools/textformat/tf_mermaid.c b/fftools/textformat/tf_mermaid.c
new file mode 100644
index 0000000000..6147cf6eea
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.c
@@ -0,0 +1,658 @@
+/*
+ * 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 "tf_internal.h"
+#include "tf_mermaid.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+
+static const char *init_directive = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 10,"
+        "\"nodeSpacing\": 10,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"flowchart\": { "
+            "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
+            "\"diagramPadding\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char* init_directive_er = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"layout\": \"elk\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 65,"
+        "\"nodeSpacing\": 60,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"er\": { "
+            "\"diagramPadding\": 12, "
+            "\"entityPadding\": 4, "
+            "\"minEntityWidth\": 150, "
+            "\"minEntityHeight\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char *theme_css_er = ""
+
+    // Variables
+            ".root { "
+                "--ff-colvideo: #6eaa7b; "
+                "--ff-colaudio: #477fb3; "
+                "--ff-colsubtitle: #ad76ab; "
+                "--ff-coltext: #666; "
+            "} "
+            " g.nodes g.node.default rect.basic.label-container, "
+            " g.nodes g.node.default path { "
+            "     rx: 1; "
+            "     ry: 1; "
+            "     stroke-width: 1px !important; "
+            "     stroke: #e9e9e9 !important; "
+            "     fill: url(#ff-filtergradient) !important; "
+            "     filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
+            "     fill: white !important; "
+            " } "
+            "  "
+            " .relationshipLine { "
+            "     stroke: gray; "
+            "     stroke-width: 1; "
+            "     fill: none; "
+            "     filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
+            " } "
+            "  "
+            " g.node.default g.label.name  foreignObject > div > span > p, "
+            " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
+            "     font-size: 0.95rem; "
+            "     font-weight: 500; "
+            "     text-transform: uppercase; "
+            "     min-width: 5.5rem; "
+            "     margin-bottom: 0.5rem; "
+            "      "
+            " } "
+            "  "
+            " .edgePaths path { "
+            "     marker-end: none; "
+            "     marker-start: none; "
+            "  "
+            "} ";
+
+
+/* Mermaid Graph output */
+
+typedef struct MermaidContext {
+    const AVClass *class;
+    AVDiagramConfig *diagram_config;
+    int subgraph_count;
+    int within_tag;
+    int indent_level;
+    int create_html;
+
+    // Options
+    int enable_link_colors; // Requires Mermaid 11.5
+
+    struct section_data {
+        const char *section_id;
+        const char *section_type;
+        const char *src_id;
+        const char *dest_id;
+        AVTextFormatLinkType link_type;
+        int current_is_textblock;
+        int current_is_stadium;
+        int subgraph_start_incomplete;
+    }  section_data[SECTION_MAX_NB_LEVELS];
+
+    unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint link_buf; ///< print buffer for writing diagram links
+    AVDictionary *link_dict;
+} MermaidContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(MermaidContext, x)
+
+static const AVOption mermaid_options[] = {
+    { "link_coloring",    "enable colored links (requires Mermaid >= 11.5)",  OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL,   { .i64 = 1 },  0, 1 },
+    ////{"diagram_css",      "CSS for the diagram",                              OFFSET(diagram_css),        AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    ////{"html_template",    "Template HTML",                                    OFFSET(html_template),      AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    { NULL },
+};
+
+DEFINE_FORMATTER_CLASS(mermaid);
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
+{
+    MermaidContext *mmc = tfc->priv;
+    mmc->diagram_config = diagram_config;
+}
+
+static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
+{
+    MermaidContext *mmc = tfc->priv;
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&buf, "%s--%s", src, dest);
+
+    if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
+        return 1;
+
+    av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
+
+    return 0;
+}
+
+static av_cold int mermaid_init(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
+    return 0;
+}
+
+static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    int ret = mermaid_init(tfc);
+
+    if (ret < 0)
+        return ret;
+
+    mmc->create_html = 1;
+
+    return 0;
+}
+
+#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
+
+static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
+{
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSectionContext *sec_ctx = data;
+
+    if (tfc->level == 0) {
+        char *directive;
+        AVBPrint css_buf;
+        const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
+        char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
+        (void)theme_css_er;
+        ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
+        av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+        av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
+        av_freep(&single_line_css);
+
+        directive = av_strireplace(diag_directive, "__###__", css_buf.str);
+        if (mmc->create_html) {
+            uint64_t length;
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+
+            length = token_pos - mmc->diagram_config->html_template;
+            for (uint64_t i = 0; i < length; i++)
+                writer_w8(tfc, mmc->diagram_config->html_template[i]);
+        }
+
+        writer_put_str(tfc, directive);
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+            writer_put_str(tfc, "flowchart LR\n");
+        ////writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
+            writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%);     \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%);   \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%);     \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            writer_put_str(tfc, "erDiagram\n");
+            break;
+        }
+
+        return;
+    }
+
+    if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
+        AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
+
+        if (parent_sec_data.subgraph_start_incomplete) {
+
+            if (parent_buf->len > 0)
+                writer_printf(tfc, "%s", parent_buf->str);
+
+            writer_put_str(tfc, "</div>\"]\n");
+
+            mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
+        }
+    }
+
+    av_freep(&mmc->section_data[tfc->level].section_id);
+    av_freep(&mmc->section_data[tfc->level].section_type);
+    av_freep(&mmc->section_data[tfc->level].src_id);
+    av_freep(&mmc->section_data[tfc->level].dest_id);
+    mmc->section_data[tfc->level].current_is_textblock = 0;
+    mmc->section_data[tfc->level].current_is_stadium = 0;
+    mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
+    mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
+
+    // NOTE: av_strdup() allocations aren't checked
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+            MM_INDENT();
+            writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+
+            mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+                if (sec_ctx->context_flags & 1) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_textblock = 1;
+                } else if (sec_ctx->context_flags & 2) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s([\"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_stadium = 1;
+                } else {
+                    MM_INDENT();
+                    writer_printf(tfc, "%s(\"", sec_ctx->context_id);
+                }
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+                MM_INDENT();
+                writer_printf(tfc, "%s {\n", sec_ctx->context_id);
+                break;
+            }
+
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
+
+        if (sec_ctx && sec_ctx->context_type)
+            writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
+        else
+            writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        av_bprint_clear(buf);
+        mmc->nb_link_captions[tfc->level] = 0;
+
+        if (sec_ctx && sec_ctx->context_type)
+            mmc->section_data[tfc->level].section_type = av_strdup(sec_ctx->context_type);
+
+        ////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(tfc, " type=\"%s\"", buf.str);
+    }
+}
+
+static void mermaid_print_section_footer(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
+        writer_put_str(tfc, "</div>");
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (sec_data.current_is_textblock) {
+                writer_printf(tfc, "\"}\n", section->name);
+
+                if (sec_data.section_id) {
+                    MM_INDENT();
+                    writer_put_str(tfc, "class ");
+                    writer_put_str(tfc, sec_data.section_id);
+                    writer_put_str(tfc, " ff-");
+                    writer_put_str(tfc, section->name);
+                    writer_put_str(tfc, "\n");
+                }
+            } else if (sec_data.current_is_stadium) {
+                writer_printf(tfc, "\"]):::ff-%s\n", section->name);
+            } else {
+                writer_printf(tfc, "\"):::ff-%s\n", section->name);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            MM_INDENT();
+            writer_put_str(tfc, "}\n\n");
+            break;
+        }
+
+        mmc->indent_level--;
+
+    } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
+
+        MM_INDENT();
+        writer_put_str(tfc, "end\n");
+
+        if (sec_data.section_id) {
+            MM_INDENT();
+            writer_put_str(tfc, "class ");
+            writer_put_str(tfc, sec_data.section_id);
+            writer_put_str(tfc, " ff-");
+            writer_put_str(tfc, section->name);
+            writer_put_str(tfc, "\n");
+        }
+
+        mmc->indent_level--;
+    }
+
+    if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
+        if (sec_data.src_id && sec_data.dest_id
+            && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+
+                if (sec_data.section_type && mmc->enable_link_colors)
+                    av_bprintf(&mmc->link_buf, "\n  %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
+                else
+                    av_bprintf(&mmc->link_buf, "\n  %s ==", sec_data.src_id);
+
+                if (buf->len > 0) {
+                    av_bprintf(&mmc->link_buf, " \"%s", buf->str);
+
+                    for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
+                        av_bprintf(&mmc->link_buf, "<br>&nbsp;");
+
+                    av_bprintf(&mmc->link_buf, "\" ==");
+                }
+
+                av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+
+                av_bprintf(&mmc->link_buf, "\n  %s", sec_data.src_id);
+
+                switch (sec_data.link_type) {
+                case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
+                    break;
+                default:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                }
+
+                av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
+
+                break;
+            }
+
+    if (tfc->level == 0) {
+
+        writer_put_str(tfc, "\n");
+        if (mmc->create_html) {
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+            token_pos += strlen("__###__");
+            writer_put_str(tfc, token_pos);
+        }
+    }
+
+    if (tfc->level == 1) {
+
+        if (mmc->link_buf.len > 0) {
+            writer_put_str(tfc, mmc->link_buf.str);
+            av_bprint_clear(&mmc->link_buf);
+        }
+
+        writer_put_str(tfc, "\n");
+    }
+}
+
+static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
+                                const char *str, int64_t num, const int is_int)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+    int exit = 0;
+
+    if (section->id_key && !strcmp(section->id_key, key)) {
+        mmc->section_data[tfc->level].section_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
+        mmc->section_data[tfc->level].dest_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->src_id_key && !strcmp(section->src_id_key, key)) {
+        mmc->section_data[tfc->level].src_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->linktype_key && !strcmp(section->linktype_key, key)) {
+        mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
+        exit = 1;
+    }
+
+    //if (exit)
+    //    return;
+
+    if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
+        || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
+
+        if (exit)
+            return;
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (is_int) {
+                writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
+            } else {
+                ////AVBPrint b;
+                ////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
+                const char *tmp = av_strireplace(str, "\"", "'");
+                ////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
+                writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
+                av_freep(&tmp);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+            if (!is_int && str)
+            {
+                const char *col_type;
+
+                if (key[0] == '_')
+                    return;
+
+                if (sec_data.section_id && !strcmp(str, sec_data.section_id))
+                    col_type = "PK";
+                else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
+                    col_type = "FK";
+                else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
+                    col_type = "FK";
+                else
+                    col_type = "";
+
+                MM_INDENT();
+
+                if (is_int)
+                    writer_printf(tfc, "    %s %"PRId64" %s\n", key, num, col_type);
+                else
+                    writer_printf(tfc, "    %s %s %s\n", key, str, col_type);
+            }
+            break;
+        }
+
+    } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        if (exit)
+            return;
+
+        if (buf->len > 0)
+            av_bprintf(buf, "%s", "<br>");
+
+        av_bprintf(buf, "");
+        if (is_int)
+            av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
+        else
+            av_bprintf(buf, "<span>%s</span>", str);
+
+        mmc->nb_link_captions[tfc->level]++;
+    }
+}
+
+static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
+{
+    mermaid_print_value(tfc, key, value, 0, 0);
+}
+
+static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
+{
+    mermaid_print_value(tfc, key, NULL, value, 1);
+}
+
+const AVTextFormatter avtextformatter_mermaid = {
+    .name                 = "mermaid",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
+
+
+const AVTextFormatter avtextformatter_mermaidhtml = {
+    .name                 = "mermaidhtml",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init_html,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
diff --git a/fftools/textformat/tf_mermaid.h b/fftools/textformat/tf_mermaid.h
new file mode 100644
index 0000000000..aff73bf9f3
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.h
@@ -0,0 +1,41 @@
+/*
+ * 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_TF_MERMAID_H
+#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
+
+typedef enum {
+    AV_DIAGRAMTYPE_GRAPH,
+    AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
+} AVDiagramType;
+
+typedef struct AVDiagramConfig {
+    AVDiagramType diagram_type;
+    const char *diagram_css;
+    const char *html_template;
+} AVDiagramConfig;
+
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
+
+void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
+
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */
\ No newline at end of file
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v4 11/11] fftools/graphprint: Now, make it a Killer-Feature!
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
                         ` (9 preceding siblings ...)
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 10/11] fftools/graphprint: Add execution graph printing softworkz
@ 2025-04-20 22:59       ` softworkz
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
  11 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-20 22:59 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

remember this: -sg   <= show-graph

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi              |   4 +
 fftools/Makefile             |   1 +
 fftools/ffmpeg.c             |   2 +-
 fftools/ffmpeg.h             |   1 +
 fftools/ffmpeg_filter.c      |   2 +-
 fftools/ffmpeg_opt.c         |   4 +
 fftools/graph/filelauncher.c | 205 +++++++++++++++++++++++++++++++++++
 fftools/graph/graphprint.c   |  50 ++++++++-
 fftools/graph/graphprint.h   |  32 ++++++
 9 files changed, 296 insertions(+), 5 deletions(-)
 create mode 100644 fftools/graph/filelauncher.c

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 35675b5309..6e9e7aed0e 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1404,6 +1404,10 @@ Writes execution graph details to the specified file in the format set via -prin
 Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
 The default format is json.
 
+@item -sg (@emph{global})
+Writes the execution graph to a temporary html file (mermaidhtml format) and 
+tries to launch it in the default browser.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index 361a4fd574..56a2910212 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -22,6 +22,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
     fftools/graph/graphprint.o        \
+    fftools/graph/filelauncher.o      \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
     fftools/textformat/avtextformat.o \
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index 6766ec209c..9875a1f7fd 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -309,7 +309,7 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
 
     if (do_benchmark) {
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7fbf0ad532..49fea0307d 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -721,6 +721,7 @@ extern int print_graphs;
 extern char *print_graphs_file;
 extern char *print_graphs_format;
 extern int auto_conversion_filters;
+extern int show_graph;
 
 extern const AVIOInterruptCB int_cb;
 
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index b774606562..e82e333b7f 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -2985,7 +2985,7 @@ read_frames:
 
 finish:
 
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraph(fg, fgt.graph);
 
     // EOF is normal termination
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3d1efe32f9..24713d640f 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -79,6 +79,7 @@ int vstats_version = 2;
 int print_graphs = 0;
 char *print_graphs_file = NULL;
 char *print_graphs_format = NULL;
+int show_graph = 0;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1748,6 +1749,9 @@ const OptionDef options[] = {
     { "print_graphs_format", OPT_TYPE_STRING, 0,
         { &print_graphs_format },
       "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
+    { "sg",   OPT_TYPE_BOOL, 0,
+        { &show_graph },
+        "create execution graph as temporary html file and try to launch it in the default browser" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/filelauncher.c b/fftools/graph/filelauncher.c
new file mode 100644
index 0000000000..0cf5f15cf1
--- /dev/null
+++ b/fftools/graph/filelauncher.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(_WIN32)
+#  include <windows.h>
+#  include <shellapi.h>
+#else
+#  include <sys/time.h>
+#  include <time.h>
+#endif
+#include "graphprint.h"
+
+int ff_open_html_in_browser(const char *html_path)
+{
+    if (!html_path || !*html_path)
+        return -1;
+
+#if defined(_WIN32)
+
+    // --- Windows ---------------------------------
+    {
+        HINSTANCE rc = ShellExecuteA(NULL, "open", html_path, NULL, NULL, SW_SHOWNORMAL);
+        if ((UINT_PTR)rc <= 32) {
+            // Fallback: system("start ...")
+            char cmd[1024];
+            _snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "start \"\" \"%s\"", html_path);
+            if (system(cmd) != 0)
+                return -1;
+        }
+        return 0;
+    }
+
+#elif defined(__APPLE__)
+
+    // --- macOS -----------------------------------
+    {
+        // "open" is the macOS command to open a file/URL with the default application
+        char cmd[1024];
+        snprintf(cmd, sizeof(cmd), "open '%s' 1>/dev/null 2>&1 &", html_path);
+        if (system(cmd) != 0)
+            return -1;
+        return 0;
+    }
+
+#else
+
+    // --- Linux / Unix-like -----------------------
+    // We'll try xdg-open, then gnome-open, then kfmclient
+    {
+        // Helper macro to try one browser command
+        // Returns 0 on success, -1 on failure
+        #define TRY_CMD(prog) do {                                   \
+            char buf[1024];                                          \
+            snprintf(buf, sizeof(buf), "%s '%s' 1>/dev/null 2>&1 &", \
+                     (prog), html_path);                              \
+            int ret = system(buf);                                    \
+            /* On Unix: system() returns -1 if the shell can't run. */\
+            /* Otherwise, check exit code in lower 8 bits.           */\
+            if (ret != -1 && WIFEXITED(ret) && WEXITSTATUS(ret) == 0) \
+                return 0;                                             \
+        } while (0)
+
+        TRY_CMD("xdg-open");
+        TRY_CMD("gnome-open");
+        TRY_CMD("kfmclient exec");
+
+        fprintf(stderr, "Could not open '%s' in a browser.\n", html_path);
+        return -1;
+    }
+
+#endif
+}
+
+
+int ff_get_temp_dir(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    // --- Windows ------------------------------------
+    {
+        // GetTempPathA returns length of the string (including trailing backslash).
+        // If the return value is greater than buffer size, it's an error.
+        DWORD len = GetTempPathA((DWORD)size, buf);
+        if (len == 0 || len > size) {
+            // Could not retrieve or buffer is too small
+            return -1;
+        }
+        return 0;
+    }
+
+#else
+
+    // --- macOS / Linux / Unix -----------------------
+    // Follow typical POSIX convention: check common env variables
+    // and fallback to /tmp if not found.
+    {
+        const char *tmp = getenv("TMPDIR");
+        if (!tmp || !*tmp) tmp = getenv("TMP");
+        if (!tmp || !*tmp) tmp = getenv("TEMP");
+        if (!tmp || !*tmp) tmp = "/tmp";
+
+        // Copy into buf, ensure there's a trailing slash
+        size_t len = strlen(tmp);
+        if (len + 2 > size) {
+            // Need up to len + 1 for slash + 1 for null terminator
+            return -1;
+        }
+
+        strcpy(buf, tmp);
+        // Append slash if necessary
+        if (buf[len - 1] != '/' && buf[len - 1] != '\\') {
+#if defined(__APPLE__)
+            // On macOS/Unix, use forward slash
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#else
+            // Technically on Unix it's always '/', but here's how you'd do if needed:
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#endif
+        }
+        return 0;
+    }
+
+#endif
+}
+
+int ff_make_timestamped_html_name(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    /*----------- Windows version -----------*/
+    SYSTEMTIME st;
+    GetLocalTime(&st);
+    /*
+      st.wYear, st.wMonth, st.wDay,
+      st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
+    */
+    int written = _snprintf_s(buf, size, _TRUNCATE,
+                              "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                              st.wYear,
+                              st.wMonth,
+                              st.wDay,
+                              st.wHour,
+                              st.wMinute,
+                              st.wSecond,
+                              st.wMilliseconds);
+    if (written < 0)
+        return -1; /* Could not write into buffer */
+    return 0;
+
+#else
+
+    /*----------- macOS / Linux / Unix version -----------*/
+    struct timeval tv;
+    if (gettimeofday(&tv, NULL) != 0) {
+        return -1; /* gettimeofday failed */
+    }
+
+    struct tm local_tm;
+    localtime_r(&tv.tv_sec, &local_tm);
+
+    int ms = (int)(tv.tv_usec / 1000); /* convert microseconds to milliseconds */
+
+    /* 
+       local_tm.tm_year is years since 1900,
+       local_tm.tm_mon  is 0-based (0=Jan, 11=Dec) 
+    */
+    int written = snprintf(buf, size,
+                           "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                           local_tm.tm_year + 1900,
+                           local_tm.tm_mon + 1,
+                           local_tm.tm_mday,
+                           local_tm.tm_hour,
+                           local_tm.tm_min,
+                           local_tm.tm_sec,
+                           ms);
+    if (written < 0 || (size_t)written >= size) {
+        return -1; /* Buffer too small or formatting error */
+    }
+    return 0;
+
+#endif
+}
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
index 89c38d2e36..8241c51e6c 100644
--- a/fftools/graph/graphprint.c
+++ b/fftools/graph/graphprint.c
@@ -586,8 +586,6 @@ static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifil
     AVBPrint                   buf;
     AVTextFormatSectionContext sec_ctx = { 0 };
 
-    sec_ctx.context_id = "Inputs";
-
     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
 
     print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
@@ -875,6 +873,11 @@ static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
 
     av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
 
+    if (show_graph) {
+        if (!print_graphs_format || strcmp(print_graphs_format, "mermaidhtml") != 0)
+            print_graphs_format = av_strdup("mermaidhtml");
+    }
+
     if (!print_graphs_format)
         print_graphs_format = av_strdup("json");
     if (!print_graphs_format) {
@@ -1098,5 +1101,46 @@ cleanup:
 
 int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
 {
-    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+    int ret;
+
+    if (show_graph) {
+        char buf[2048];
+        AVBPrint bp;
+
+        av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+        print_graphs = 0;
+
+        ret = ff_get_temp_dir(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error getting temp directory path for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        ret = ff_make_timestamped_html_name(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error creating temp file name for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        av_bprint_finalize(&bp, &print_graphs_file);
+    }
+
+    ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    if (!ret && show_graph) {
+        av_log(NULL, AV_LOG_INFO, "Execution graph saved as: %s\n", print_graphs_file);
+        av_log(NULL, AV_LOG_INFO, "Trying to launch graph in browser...\n");
+
+        ret = ff_open_html_in_browser(print_graphs_file);
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Browser could not be launched for execution graph display\nPlease open manually: %s\n", print_graphs_file);
+        }
+    }
+
+    return ret;
 }
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
index 9f043cc273..43f769870b 100644
--- a/fftools/graph/graphprint.h
+++ b/fftools/graph/graphprint.h
@@ -27,4 +27,36 @@ int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles,
 
 int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
 
+/**
+ * Open an HTML file in the default browser (Windows, macOS, Linux/Unix).
+ *
+ * @param html_path Absolute or relative path to the HTML file.
+ * @return 0 on success, -1 on failure.
+ *
+ * NOTE: This uses system() calls for non-Windows, and ShellExecute on Windows.
+ *       Exercise caution if 'html_path' is untrusted (possible command injection).
+ */
+int ff_open_html_in_browser(const char *html_path);
+
+/**
+ * Retrieve the system's temporary directory.
+ *
+ * @param buf  Output buffer to store the temp directory path (including trailing slash)
+ * @param size Size of the output buffer in bytes
+ * @return 0 on success, -1 on failure (buffer too small or other errors)
+ *
+ * Note: On most platforms, the path will include a trailing slash (e.g. "C:\\Users\\...\\Temp\\" on Windows, "/tmp/" on Unix).
+ */
+int ff_get_temp_dir(char *buf, size_t size);
+
+/**
+ * Create a timestamped HTML filename, e.g.:
+ *   ffmpeg_graph_2024-01-01_22-12-59_123.html
+ *
+ * @param buf  Pointer to buffer where the result is stored
+ * @param size Size of the buffer in bytes
+ * @return 0 on success, -1 on error (e.g. buffer too small)
+ */
+int ff_make_timestamped_html_name(char *buf, size_t size);
+
 #endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH 1/9] fftools/textformat: Formatting and whitespace changes
  2025-04-14 12:46 ` [FFmpeg-devel] [PATCH 1/9] fftools/textformat: Formatting and whitespace changes softworkz
@ 2025-04-21 16:52   ` Stefano Sabatini
  2025-04-21 17:12     ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-21 16:52 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Monday 2025-04-14 12:46:58 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/avtextformat.c  | 104 +++++++++++++++--------------
>  fftools/textformat/avtextformat.h  |  16 ++---
>  fftools/textformat/avtextwriters.h |  11 ++-
>  fftools/textformat/tf_compact.c    |  91 ++++++++++++++-----------
>  fftools/textformat/tf_default.c    |  20 +++---
>  fftools/textformat/tf_flat.c       |  26 ++++----
>  fftools/textformat/tf_ini.c        |  36 +++++-----
>  fftools/textformat/tf_json.c       |  10 +--
>  fftools/textformat/tf_xml.c        |  30 +++++----
>  9 files changed, 183 insertions(+), 161 deletions(-)
>

[...]
> -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)
> +int avtext_context_open(AVTextFormatContext      **ptctx,
> +                        const AVTextFormatter     *formatter,
> +                        AVTextWriterContext       *writer_context,
> +                        const char                *args,
> +                        const 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)

This is possibly one of the few places where we align
parameters. Unrelated note: the function also has too many parameters,
probably they should be merged in a flags field.

>  {
>      AVTextFormatContext *tctx;
>      int i, ret = 0;
> @@ -197,7 +201,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
>          av_dict_free(&opts);
>      }
>  
> -    if (show_data_hash) {

> +    if (show_data_hash)
>          if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
>              if (ret == AVERROR(EINVAL)) {
>                  const char *n;
> @@ -208,7 +212,6 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
>              }
>              return ret;
>          }
> -    }

I'm a bit against dropping {} since this doesn't add to redability in
most cases and can lead to bugs in case more statements are added in
the if block (since it's easy to miss the wrapping parentheses
addition).
  
[...]
> @@ -81,6 +81,7 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
>  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)
> @@ -117,16 +118,16 @@ typedef struct 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},
> +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 },

About this kind of changes:
{FIELDS...} => { FIELDS... }

I'm not against it but I wonder if there are coding style guidelines
to follow or we end up with every single file using a different style
- we might even enforce or automate some of them through the use of
tooling.

[...]

Rest of the changes looks good to me, thanks.
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH 1/9] fftools/textformat: Formatting and whitespace changes
  2025-04-21 16:52   ` Stefano Sabatini
@ 2025-04-21 17:12     ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-21 17:12 UTC (permalink / raw)
  To: Stefano Sabatini, FFmpeg development discussions and patches



> -----Original Message-----
> From: Stefano Sabatini <stefasab@gmail.com>
> Sent: Montag, 21. April 2025 18:53
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Cc: softworkz <softworkz@hotmail.com>
> Subject: Re: [FFmpeg-devel] [PATCH 1/9] fftools/textformat: Formatting
> and whitespace changes
> 
> On date Monday 2025-04-14 12:46:58 +0000, softworkz wrote:
> > From: softworkz <softworkz@hotmail.com>
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  fftools/textformat/avtextformat.c  | 104 +++++++++++++++-----------
> ---
> >  fftools/textformat/avtextformat.h  |  16 ++---
> >  fftools/textformat/avtextwriters.h |  11 ++-
> >  fftools/textformat/tf_compact.c    |  91 ++++++++++++++-----------
> >  fftools/textformat/tf_default.c    |  20 +++---
> >  fftools/textformat/tf_flat.c       |  26 ++++----
> >  fftools/textformat/tf_ini.c        |  36 +++++-----
> >  fftools/textformat/tf_json.c       |  10 +--
> >  fftools/textformat/tf_xml.c        |  30 +++++----
> >  9 files changed, 183 insertions(+), 161 deletions(-)
> >
> 
> [...]
> > -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)
> > +int avtext_context_open(AVTextFormatContext      **ptctx,
> > +                        const AVTextFormatter     *formatter,
> > +                        AVTextWriterContext       *writer_context,
> > +                        const char                *args,
> > +                        const 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)
> 
> This is possibly one of the few places where we align
> parameters. Unrelated note: the function also has too many parameters,
> probably they should be merged in a flags field.

Or an options struct maybe?


> 
> >  {
> >      AVTextFormatContext *tctx;
> >      int i, ret = 0;
> > @@ -197,7 +201,7 @@ int avtext_context_open(AVTextFormatContext
> **ptctx, const AVTextFormatter *form
> >          av_dict_free(&opts);
> >      }
> >
> > -    if (show_data_hash) {
> 
> > +    if (show_data_hash)
> >          if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0)
> {
> >              if (ret == AVERROR(EINVAL)) {
> >                  const char *n;
> > @@ -208,7 +212,6 @@ int avtext_context_open(AVTextFormatContext
> **ptctx, const AVTextFormatter *form
> >              }
> >              return ret;
> >          }
> > -    }
> 
> I'm a bit against dropping {} since this doesn't add to redability in
> most cases and can lead to bugs in case more statements are added in
> the if block (since it's easy to miss the wrapping parentheses
> addition).

I thought the rules would say that "if" blocks should only be parenthesized
when it has multiple statements OR there's an "else" clause.

Personally, I would always use parenthesizes, but that's of little 
relevance when the codebase has different rules.


> 
> [...]
> > @@ -81,6 +81,7 @@ static const char *c_escape_str(AVBPrint *dst,
> const char *src, const char sep,
> >  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)
> > @@ -117,16 +118,16 @@ typedef struct 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},
> > +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 },
> 
> About this kind of changes:
> {FIELDS...} => { FIELDS... }
> 
> I'm not against it but I wonder if there are coding style guidelines
> to follow or we end up with every single file using a different style
> - we might even enforce or automate some of them through the use of
> tooling.

I had started an attempt to create .clang-format and .clang-tidy config
files that match the formatting and warnings to the FFmpeg code base.
I got stuck with the second part after I had determined that a number of
the clang-tidy warnings are useful during work even though these are 
warning about things that are done at countless places.

But I can share the .clang-format file for review and further refinement
when there's interest. I think this would be beneficial and would allow
to achieve a higher degree of consistency.


> Rest of the changes looks good to me, thanks.


Thanks a lot for looking at it!
sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat: Quality improvements
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat: Quality improvements softworkz
@ 2025-04-21 17:16         ` Stefano Sabatini
  2025-04-21 17:21           ` Nicolas George
  2025-04-21 17:29           ` softworkz .
  0 siblings, 2 replies; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-21 17:16 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Sunday 2025-04-20 22:59:05 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 

Nazigrammar commit log nit: use verb to specify the change action,
like in: apply quality improvements

Also probably want to have some more details below the headline,
like:

Perform multiple improvements to increase code robustness.
In particular:
* favor unsigned counters for loops
* add missing checks
* avoid possibly leaks
* move variable declarations to inner scopes when feasible
* provide explicit type-casting when needed
...

> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/avtextformat.c | 111 +++++++++++++++++++-----------
>  fftools/textformat/avtextformat.h |   6 +-
>  fftools/textformat/tf_default.c   |   8 ++-
>  fftools/textformat/tf_ini.c       |   2 +-
>  fftools/textformat/tf_json.c      |   8 ++-
>  fftools/textformat/tf_xml.c       |   3 -
>  fftools/textformat/tw_avio.c      |   9 ++-
>  7 files changed, 93 insertions(+), 54 deletions(-)
> 
> diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
> index edbcd0b342..893b11298e 100644
> --- a/fftools/textformat/avtextformat.c
> +++ b/fftools/textformat/avtextformat.c
> @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
>  
>  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++)
> +    for (unsigned i = 0; i < ubuf_size; i++)
>          av_bprintf(bp, "%02X", ubuf[i]);
>  }
>  
> @@ -141,7 +140,10 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
>      AVTextFormatContext *tctx;
>      int i, ret = 0;
>  
> -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> +    if (!ptctx || !formatter)
> +        return AVERROR(EINVAL);
> +

> +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {

Sorry to nitpick and if I miss the past discussion, why the added
parentheses?

>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
> @@ -213,25 +215,26 @@ int avtext_context_open(AVTextFormatContext      **ptctx,
>                      av_log(NULL, AV_LOG_ERROR, " %s", n);
>                  av_log(NULL, AV_LOG_ERROR, "\n");
>              }
> -            return ret;
> +            goto fail;
>          }
>  
>      /* validate replace string */
>      {
> -        const uint8_t *p = tctx->string_validation_replacement;
> -        const uint8_t *endp = p + strlen(p);
> +        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
> +        const uint8_t *endp = p + strlen((const char *)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);
> +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>                  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;
> +                av_bprint_finalize(&bp, NULL);
> +                goto fail;
>              }
>          }
>      }
> @@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
>  
>  void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
>  {
> +    if (section_id < 0 || section_id >= tctx->nb_sections)
> +        return;
> +
>      tctx->level++;
>      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
>  
> @@ -272,6 +278,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
>  
>  void avtext_print_section_footer(AVTextFormatContext *tctx)
>  {
> +    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return;
> +
>      int section_id = tctx->section[tctx->level]->id;
>      int parent_section_id = tctx->level
>          ? tctx->section[tctx->level - 1]->id
> @@ -289,7 +298,12 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
>  
>  void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
>  {
> -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
> +    const AVTextFormatSection *section;
> +

> +    if (!key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return;

possibly unrelated: maybe we should add an explicit warning or even an
assert?

> +
> +    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);
> @@ -299,24 +313,25 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
>  

>  static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
>  {
> -    const uint8_t *p, *endp;
> +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
>      AVBPrint dstbuf;
> +    AVBPrint bp;
>      int invalid_chars_nb = 0, ret = 0;
>  
> +    *dstp = NULL;
>      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
> +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>  
> -    endp = src + strlen(src);
> -    for (p = src; *p;) {
> -        uint32_t code;
> +    endp = srcp + strlen(src);
> +    for (p = srcp; *p;) {
> +        int32_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);
> +            av_bprint_clear(&bp);
> +            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;
>          }
>  
> @@ -336,7 +351,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
>          }
>  
>          if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
> -            av_bprint_append_data(&dstbuf, p0, p-p0);
> +            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
>      }
>  
>      if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
> @@ -346,6 +361,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
>  
>  end:
>      av_bprint_finalize(&dstbuf, dstp);
> +    av_bprint_finalize(&bp, NULL);
>      return ret;
>  }

Please split this to a dedicated commit, we want to have a
justification for this one in the commit log.

>  
> @@ -358,17 +374,18 @@ struct unit_value {
>      const char *unit;
>  };
>  
> -static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
> +static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
>  {
>      double vald;
> -    int64_t vali;
> +    int64_t vali = 0;
>      int show_float = 0;
>  
>      if (uv.unit == unit_second_str) {
>          vald = uv.val.d;
>          show_float = 1;
>      } else {
> -        vald = vali = uv.val.i;
> +        vald = (double)uv.val.i;
> +        vali = uv.val.i;
>      }
>  
>      if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
> @@ -387,17 +404,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
>              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);
> +                index = (int64_t)(log2(vald) / 10);
> +                index = av_clip64(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);
> +                index = (int64_t)(log10(vald) / 3);
> +                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
>                  vald /= si_prefixes[index].dec_val;
>                  prefix_string = si_prefixes[index].dec_str;
>              }
> -            vali = vald;
> +            vali = (int64_t)vald;
>          }
>  
>          if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
> @@ -425,9 +442,14 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
>  
>  int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
>  {
> -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
> +    const AVTextFormatSection *section;
>      int ret = 0;
>  

> +    if (!key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return AVERROR(EINVAL);
> +
> +    section = tctx->section[tctx->level];

ditto

> +
>      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
>          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
>              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
> @@ -469,12 +491,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
>  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);
> +        char buf[128];
> +        double d = av_q2d(*time_base) * ts;
>          struct unit_value uv;
>          uv.val.d = d;
>          uv.unit = unit_second_str;
> @@ -495,7 +516,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
>                         const uint8_t *data, int size)
>  {
>      AVBPrint bp;
> -    int offset = 0, l, i;
> +    unsigned offset = 0;
> +    int l, i;
>  
>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>      av_bprintf(&bp, "\n");
> @@ -522,25 +544,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
>  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 };
> +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
> +    int len;
>  
>      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);
> +    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
> +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
>      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)
> +                           uint8_t *data, int size, const char *format,
> +                           int columns, int bytes, int offset_add)
>  {
>      AVBPrint bp;
> -    int offset = 0, l, i;
> +    unsigned offset = 0;
> +    int l, i;
> +
> +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
> +        return;
>  
>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>      av_bprintf(&bp, "\n");
> @@ -606,12 +632,15 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
>      AVTextWriterContext *wctx;
>      int ret = 0;
>  
> -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
> +    if (!pwctx || !writer)
> +        return AVERROR(EINVAL);
> +
> +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
>  
> -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
> +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
> diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
> index c598af3450..aea691f351 100644
> --- a/fftools/textformat/avtextformat.h
> +++ b/fftools/textformat/avtextformat.h
> @@ -21,9 +21,7 @@
>  #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"
> @@ -103,7 +101,7 @@ struct AVTextFormatContext {
>      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];
> +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
>      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
>                                                    ///  used by various formatters
>  
> @@ -124,7 +122,7 @@ struct AVTextFormatContext {
>  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
>  
>  int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
> -                        const struct AVTextFormatSection *sections, int nb_sections,
> +                        const AVTextFormatSection *sections, int nb_sections,
>                          int show_value_unit,
>                          int use_value_prefix,
>                          int use_byte_value_binary_prefix,
> diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
> index 2c5047eafd..ad97173b0b 100644
> --- a/fftools/textformat/tf_default.c
> +++ b/fftools/textformat/tf_default.c
> @@ -68,9 +68,10 @@ 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;
> +    unsigned i;
> +
>      for (i = 0; src[i] && i < dst_size - 1; i++)
> -        dst[i] = av_toupper(src[i]);
> +        dst[i] = (char)av_toupper(src[i]);
>      dst[i] = 0;
>      return dst;
>  }
> @@ -106,6 +107,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
>      const struct AVTextFormatSection *section = wctx->section[wctx->level];
>      char buf[32];
>  
> +    if (!section)
> +        return;
> +
>      if (def->noprint_wrappers || def->nested_section[wctx->level])
>          return;
>  
> diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
> index 88add0819a..dd77d0e8bf 100644
> --- a/fftools/textformat/tf_ini.c
> +++ b/fftools/textformat/tf_ini.c
> @@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
>              /* fallthrough */
>          default:
>              if ((unsigned char)c < 32)
> -                av_bprintf(dst, "\\x00%02x", c & 0xff);
> +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
>              else
>                  av_bprint_chars(dst, c, 1);
>              break;
> diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
> index b61d3740c6..e86cdbf5d9 100644
> --- a/fftools/textformat/tf_json.c
> +++ b/fftools/textformat/tf_json.c
> @@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
>      static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
>      const char *p;
>  

> +    if (!src) {
> +        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source string\n");

nit++: warning?

About error semantic policy, from a quick grep it looks like FFmpeg
codebase avoids references to the function name, and only provides a
simple error description, we might do with:

Cannot escape NULL string, returning NULL

> +        return NULL;
> +    }
> +
>      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);
> +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
>          } else {
>              av_bprint_chars(dst, *p, 1);
>          }
> @@ -105,6 +110,7 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
>          wctx->section[wctx->level-1] : NULL;
>  
>      if (wctx->level && wctx->nb_item[wctx->level-1])
> +    if (wctx->level && wctx->nb_item[wctx->level - 1])
>          writer_put_str(wctx, ",\n");
>  
>      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
> diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
> index befb39246d..28abfc6400 100644
> --- a/fftools/textformat/tf_xml.c
> +++ b/fftools/textformat/tf_xml.c
> @@ -18,10 +18,7 @@
>   * 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"
> diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
> index 6034f74ec9..d1b494b7b4 100644
> --- a/fftools/textformat/tw_avio.c
> +++ b/fftools/textformat/tw_avio.c
> @@ -53,7 +53,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
>  static void io_put_str(AVTextWriterContext *wctx, const char *str)
>  {
>      IOWriterContext *ctx = wctx->priv;
> -    avio_write(ctx->avio_context, str, strlen(str));
> +    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
>  }
>  
>  static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> @@ -78,10 +78,12 @@ const AVTextWriter avtextwriter_avio = {
>  
>  int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
>  {

> +    if (!output_filename || !output_filename[0])
> +        return AVERROR(EINVAL);

I'd add a warning to aid debugging.

> +
>      IOWriterContext *ctx;
>      int ret;
>  
> -
>      ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
>      if (ret < 0)
>          return ret;
> @@ -103,6 +105,9 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
>  
>  int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
>  {

> +    if (!pwctx || !avio_ctx)
> +        return AVERROR(EINVAL);
> +

maybe warning in this case as well

[...]

Looks good to me otherwise.
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat: Quality improvements
  2025-04-21 17:16         ` Stefano Sabatini
@ 2025-04-21 17:21           ` Nicolas George
  2025-04-21 17:40             ` softworkz .
  2025-04-21 17:29           ` softworkz .
  1 sibling, 1 reply; 130+ messages in thread
From: Nicolas George @ 2025-04-21 17:21 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

Stefano Sabatini (HE12025-04-21):
> > +    if (!pwctx || !avio_ctx)
> > +        return AVERROR(EINVAL);
> > +
> maybe warning in this case as well

Checking that an argument that must not be null is not null is the
responsibility of the caller: crashing here is the right thing to do,
same as fprintf(NULL, "fmt").

Therefore, an assertion is the best choice.

Regards,

-- 
  Nicolas George
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 03/11] fftools/textformat: Introduce common header and deduplicate code
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 03/11] fftools/textformat: Introduce common header and deduplicate code softworkz
@ 2025-04-21 17:28         ` Stefano Sabatini
  2025-04-21 17:31           ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-21 17:28 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Sunday 2025-04-20 22:59:06 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>

first part should be good

[...]


> diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
> index d1b494b7b4..48868ebf5d 100644
> --- a/fftools/textformat/tw_avio.c
> +++ b/fftools/textformat/tw_avio.c
> @@ -56,14 +56,11 @@ static void io_put_str(AVTextWriterContext *wctx, const char *str)
>      avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
>  }
>  
> -static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> +static void io_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
>  {
>      IOWriterContext *ctx = wctx->priv;
> -    va_list ap;
>  
> -    va_start(ap, fmt);
> -    avio_vprintf(ctx->avio_context, fmt, ap);
> -    va_end(ap);
> +    avio_vprintf(ctx->avio_context, fmt, vl);
>  }
>  
>  
> diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
> index f8b38414a6..f861722247 100644
> --- a/fftools/textformat/tw_buffer.c
> +++ b/fftools/textformat/tw_buffer.c
> @@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
>      av_bprintf(ctx->buffer, "%s", str);
>  }
>  
> -static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> +static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
>  {
>      BufferWriterContext *ctx = wctx->priv;
>  
> -    va_list vargs;
> -    va_start(vargs, fmt);
> -    av_vbprintf(ctx->buffer, fmt, vargs);
> -    va_end(vargs);
> +    av_vbprintf(ctx->buffer, fmt, vl);
>  }
>  
>  
> diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
> index 23de6f671f..dace55f38a 100644
> --- a/fftools/textformat/tw_stdout.c
> +++ b/fftools/textformat/tw_stdout.c
> @@ -53,13 +53,9 @@ static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
>      printf("%s", str);
>  }
>  
> -static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> +static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
>  {
> -    va_list ap;
> -
> -    va_start(ap, fmt);
> -    vprintf(fmt, ap);
> -    va_end(ap);
> +    vprintf(fmt, vl);
>  }

This looks like a logically different change and I'd rather move to a
dedicated commit.
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat: Quality improvements
  2025-04-21 17:16         ` Stefano Sabatini
  2025-04-21 17:21           ` Nicolas George
@ 2025-04-21 17:29           ` softworkz .
  1 sibling, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-21 17:29 UTC (permalink / raw)
  To: Stefano Sabatini, FFmpeg development discussions and patches



> -----Original Message-----
> From: Stefano Sabatini <stefasab@gmail.com>
> Sent: Montag, 21. April 2025 19:17
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Cc: softworkz <softworkz@hotmail.com>
> Subject: Re: [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat:
> Quality improvements
> 
> On date Sunday 2025-04-20 22:59:05 +0000, softworkz wrote:
> > From: softworkz <softworkz@hotmail.com>
> >
> 
> Nazigrammar commit log nit: use verb to specify the change action,
> like in: apply quality improvements

Sure.

> 
> Also probably want to have some more details below the headline,
> like:
> 
> Perform multiple improvements to increase code robustness.
> In particular:
> * favor unsigned counters for loops
> * add missing checks
> * avoid possibly leaks
> * move variable declarations to inner scopes when feasible
> * provide explicit type-casting when needed

Yea, that's better. Many thanks for writing it out!


> 
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  fftools/textformat/avtextformat.c | 111 +++++++++++++++++++--------
> ---
> >  fftools/textformat/avtextformat.h |   6 +-
> >  fftools/textformat/tf_default.c   |   8 ++-
> >  fftools/textformat/tf_ini.c       |   2 +-
> >  fftools/textformat/tf_json.c      |   8 ++-
> >  fftools/textformat/tf_xml.c       |   3 -
> >  fftools/textformat/tw_avio.c      |   9 ++-
> >  7 files changed, 93 insertions(+), 54 deletions(-)
> >
> > diff --git a/fftools/textformat/avtextformat.c
> b/fftools/textformat/avtextformat.c
> > index edbcd0b342..893b11298e 100644
> > --- a/fftools/textformat/avtextformat.c
> > +++ b/fftools/textformat/avtextformat.c
> > @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
> >
> >  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++)
> > +    for (unsigned i = 0; i < ubuf_size; i++)
> >          av_bprintf(bp, "%02X", ubuf[i]);
> >  }
> >
> > @@ -141,7 +140,10 @@ int avtext_context_open(AVTextFormatContext
> **ptctx,
> >      AVTextFormatContext *tctx;
> >      int i, ret = 0;
> >
> > -    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> > +    if (!ptctx || !formatter)
> > +        return AVERROR(EINVAL);
> > +
> 
> > +    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
> 
> Sorry to nitpick and if I miss the past discussion, why the added
> parentheses?

https://clang.llvm.org/extra/clang-tidy/checks/bugprone/assignment-in-if-condition.html


> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> > @@ -213,25 +215,26 @@ int avtext_context_open(AVTextFormatContext
> **ptctx,
> >                      av_log(NULL, AV_LOG_ERROR, " %s", n);
> >                  av_log(NULL, AV_LOG_ERROR, "\n");
> >              }
> > -            return ret;
> > +            goto fail;
> >          }
> >
> >      /* validate replace string */
> >      {
> > -        const uint8_t *p = tctx->string_validation_replacement;
> > -        const uint8_t *endp = p + strlen(p);
> > +        const uint8_t *p = (uint8_t *)tctx-
> >string_validation_replacement;
> > +        const uint8_t *endp = p + strlen((const char *)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);
> > +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >                  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;
> > +                av_bprint_finalize(&bp, NULL);
> > +                goto fail;
> >              }
> >          }
> >      }
> > @@ -259,6 +262,9 @@ static const char unit_bit_per_second_str[] =
> "bit/s";
> >
> >  void avtext_print_section_header(AVTextFormatContext *tctx, const
> void *data, int section_id)
> >  {
> > +    if (section_id < 0 || section_id >= tctx->nb_sections)
> > +        return;
> > +
> >      tctx->level++;
> >      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
> >
> > @@ -272,6 +278,9 @@ void
> avtext_print_section_header(AVTextFormatContext *tctx, const void
> *data, in
> >
> >  void avtext_print_section_footer(AVTextFormatContext *tctx)
> >  {
> > +    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> > +        return;
> > +
> >      int section_id = tctx->section[tctx->level]->id;
> >      int parent_section_id = tctx->level
> >          ? tctx->section[tctx->level - 1]->id
> > @@ -289,7 +298,12 @@ void
> avtext_print_section_footer(AVTextFormatContext *tctx)
> >
> >  void avtext_print_integer(AVTextFormatContext *tctx, const char
> *key, int64_t val)
> >  {
> > -    const struct AVTextFormatSection *section = tctx->section[tctx-
> >level];
> > +    const AVTextFormatSection *section;
> > +
> 
> > +    if (!key || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return;
> 
> possibly unrelated: maybe we should add an explicit warning or even an
> assert?

I agree, this is actually something that must not happen, not something
that should be silently ignored.


> 
> > +
> > +    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);
> > @@ -299,24 +313,25 @@ void avtext_print_integer(AVTextFormatContext
> *tctx, const char *key, int64_t va
> >
> 
> >  static inline int validate_string(AVTextFormatContext *tctx, char
> **dstp, const char *src)
> >  {
> > -    const uint8_t *p, *endp;
> > +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
> >      AVBPrint dstbuf;
> > +    AVBPrint bp;
> >      int invalid_chars_nb = 0, ret = 0;
> >
> > +    *dstp = NULL;
> >      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
> > +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >
> > -    endp = src + strlen(src);
> > -    for (p = src; *p;) {
> > -        uint32_t code;
> > +    endp = srcp + strlen(src);
> > +    for (p = srcp; *p;) {
> > +        int32_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);
> > +            av_bprint_clear(&bp);
> > +            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;
> >          }
> >
> > @@ -336,7 +351,7 @@ static inline int
> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >          }
> >
> >          if (!invalid || tctx->string_validation ==
> AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
> > -            av_bprint_append_data(&dstbuf, p0, p-p0);
> > +            av_bprint_append_data(&dstbuf, (const char *)p0, p -
> p0);
> >      }
> >
> >      if (invalid_chars_nb && tctx->string_validation ==
> AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
> > @@ -346,6 +361,7 @@ static inline int
> validate_string(AVTextFormatContext *tctx, char **dstp, const
> >
> >  end:
> >      av_bprint_finalize(&dstbuf, dstp);
> > +    av_bprint_finalize(&bp, NULL);
> >      return ret;
> >  }
> 
> Please split this to a dedicated commit, we want to have a
> justification for this one in the commit log.

Ok, will do.



> > @@ -358,17 +374,18 @@ struct unit_value {
> >      const char *unit;
> >  };
> >
> > -static char *value_string(AVTextFormatContext *tctx, char *buf, int
> buf_size, struct unit_value uv)
> > +static char *value_string(const AVTextFormatContext *tctx, char
> *buf, int buf_size, struct unit_value uv)
> >  {
> >      double vald;
> > -    int64_t vali;
> > +    int64_t vali = 0;
> >      int show_float = 0;
> >
> >      if (uv.unit == unit_second_str) {
> >          vald = uv.val.d;
> >          show_float = 1;
> >      } else {
> > -        vald = vali = uv.val.i;
> > +        vald = (double)uv.val.i;
> > +        vali = uv.val.i;
> >      }
> >
> >      if (uv.unit == unit_second_str && tctx-
> >use_value_sexagesimal_format) {
> > @@ -387,17 +404,17 @@ static char *value_string(AVTextFormatContext
> *tctx, char *buf, int buf_size, st
> >              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);
> > +                index = (int64_t)(log2(vald) / 10);
> > +                index = av_clip64(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);
> > +                index = (int64_t)(log10(vald) / 3);
> > +                index = av_clip64(index, 0,
> FF_ARRAY_ELEMS(si_prefixes) - 1);
> >                  vald /= si_prefixes[index].dec_val;
> >                  prefix_string = si_prefixes[index].dec_str;
> >              }
> > -            vali = vald;
> > +            vali = (int64_t)vald;
> >          }
> >
> >          if (show_float || (tctx->use_value_prefix && vald !=
> (int64_t)vald))
> > @@ -425,9 +442,14 @@ void avtext_print_unit_int(AVTextFormatContext
> *tctx, const char *key, int value
> >
> >  int avtext_print_string(AVTextFormatContext *tctx, const char *key,
> const char *val, int flags)
> >  {
> > -    const struct AVTextFormatSection *section = tctx->section[tctx-
> >level];
> > +    const AVTextFormatSection *section;
> >      int ret = 0;
> >
> 
> > +    if (!key || !val || tctx->level < 0 || tctx->level >=
> SECTION_MAX_NB_LEVELS)
> > +        return AVERROR(EINVAL);
> > +
> > +    section = tctx->section[tctx->level];
> 
> ditto

OK


> >      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
> >          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
> >              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
> > @@ -469,12 +491,11 @@ void avtext_print_rational(AVTextFormatContext
> *tctx, const char *key, AVRationa
> >  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);
> > +        char buf[128];
> > +        double d = av_q2d(*time_base) * ts;
> >          struct unit_value uv;
> >          uv.val.d = d;
> >          uv.unit = unit_second_str;
> > @@ -495,7 +516,8 @@ void avtext_print_data(AVTextFormatContext
> *tctx, const char *name,
> >                         const uint8_t *data, int size)
> >  {
> >      AVBPrint bp;
> > -    int offset = 0, l, i;
> > +    unsigned offset = 0;
> > +    int l, i;
> >
> >      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >      av_bprintf(&bp, "\n");
> > @@ -522,25 +544,29 @@ void avtext_print_data(AVTextFormatContext
> *tctx, const char *name,
> >  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 };
> > +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
> > +    int len;
> >
> >      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);
> > +    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx-
> >hash));
> > +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len],
> (int)sizeof(buf) - len);
> >      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)
> > +                           uint8_t *data, int size, const char
> *format,
> > +                           int columns, int bytes, int offset_add)
> >  {
> >      AVBPrint bp;
> > -    int offset = 0, l, i;
> > +    unsigned offset = 0;
> > +    int l, i;
> > +
> > +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
> > +        return;
> >
> >      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >      av_bprintf(&bp, "\n");
> > @@ -606,12 +632,15 @@ int
> avtextwriter_context_open(AVTextWriterContext **pwctx, const
> AVTextWriter *w
> >      AVTextWriterContext *wctx;
> >      int ret = 0;
> >
> > -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
> > +    if (!pwctx || !writer)
> > +        return AVERROR(EINVAL);
> > +
> > +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> >
> > -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
> > +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer-
> >priv_size)))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> >      }
> > diff --git a/fftools/textformat/avtextformat.h
> b/fftools/textformat/avtextformat.h
> > index c598af3450..aea691f351 100644
> > --- a/fftools/textformat/avtextformat.h
> > +++ b/fftools/textformat/avtextformat.h
> > @@ -21,9 +21,7 @@
> >  #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"
> > @@ -103,7 +101,7 @@ struct AVTextFormatContext {
> >      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];
> > +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
> >      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic
> print buffer dedicated to each section,
> >                                                    ///  used by
> various formatters
> >
> > @@ -124,7 +122,7 @@ struct AVTextFormatContext {
> >  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
> >
> >  int avtext_context_open(AVTextFormatContext **ptctx, const
> AVTextFormatter *formatter, AVTextWriterContext *writer_context, const
> char *args,
> > -                        const struct AVTextFormatSection *sections,
> int nb_sections,
> > +                        const AVTextFormatSection *sections, int
> nb_sections,
> >                          int show_value_unit,
> >                          int use_value_prefix,
> >                          int use_byte_value_binary_prefix,
> > diff --git a/fftools/textformat/tf_default.c
> b/fftools/textformat/tf_default.c
> > index 2c5047eafd..ad97173b0b 100644
> > --- a/fftools/textformat/tf_default.c
> > +++ b/fftools/textformat/tf_default.c
> > @@ -68,9 +68,10 @@ 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;
> > +    unsigned i;
> > +
> >      for (i = 0; src[i] && i < dst_size - 1; i++)
> > -        dst[i] = av_toupper(src[i]);
> > +        dst[i] = (char)av_toupper(src[i]);
> >      dst[i] = 0;
> >      return dst;
> >  }
> > @@ -106,6 +107,9 @@ static void
> default_print_section_footer(AVTextFormatContext *wctx)
> >      const struct AVTextFormatSection *section = wctx->section[wctx-
> >level];
> >      char buf[32];
> >
> > +    if (!section)
> > +        return;
> > +
> >      if (def->noprint_wrappers || def->nested_section[wctx->level])
> >          return;
> >
> > diff --git a/fftools/textformat/tf_ini.c
> b/fftools/textformat/tf_ini.c
> > index 88add0819a..dd77d0e8bf 100644
> > --- a/fftools/textformat/tf_ini.c
> > +++ b/fftools/textformat/tf_ini.c
> > @@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const
> char *src)
> >              /* fallthrough */
> >          default:
> >              if ((unsigned char)c < 32)
> > -                av_bprintf(dst, "\\x00%02x", c & 0xff);
> > +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
> >              else
> >                  av_bprint_chars(dst, c, 1);
> >              break;
> > diff --git a/fftools/textformat/tf_json.c
> b/fftools/textformat/tf_json.c
> > index b61d3740c6..e86cdbf5d9 100644
> > --- a/fftools/textformat/tf_json.c
> > +++ b/fftools/textformat/tf_json.c
> > @@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint
> *dst, const char *src, void *log_ctx
> >      static const char json_subst[]  = { '"', '\\',  'b',  'f',
> 'n',  'r',  't', 0 };
> >      const char *p;
> >
> 
> > +    if (!src) {
> > +        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source
> string\n");
> 
> nit++: warning?
> 
> About error semantic policy, from a quick grep it looks like FFmpeg
> codebase avoids references to the function name, and only provides a
> simple error description, we might do with:
> 
> Cannot escape NULL string, returning NULL

Alright.



> 
> > +        return NULL;
> > +    }
> > +
> >      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);
> > +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
> >          } else {
> >              av_bprint_chars(dst, *p, 1);
> >          }
> > @@ -105,6 +110,7 @@ static void
> json_print_section_header(AVTextFormatContext *wctx, const void *dat
> >          wctx->section[wctx->level-1] : NULL;
> >
> >      if (wctx->level && wctx->nb_item[wctx->level-1])
> > +    if (wctx->level && wctx->nb_item[wctx->level - 1])
> >          writer_put_str(wctx, ",\n");
> >
> >      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
> > diff --git a/fftools/textformat/tf_xml.c
> b/fftools/textformat/tf_xml.c
> > index befb39246d..28abfc6400 100644
> > --- a/fftools/textformat/tf_xml.c
> > +++ b/fftools/textformat/tf_xml.c
> > @@ -18,10 +18,7 @@
> >   * 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"
> > diff --git a/fftools/textformat/tw_avio.c
> b/fftools/textformat/tw_avio.c
> > index 6034f74ec9..d1b494b7b4 100644
> > --- a/fftools/textformat/tw_avio.c
> > +++ b/fftools/textformat/tw_avio.c
> > @@ -53,7 +53,7 @@ static void io_w8(AVTextWriterContext *wctx, int
> b)
> >  static void io_put_str(AVTextWriterContext *wctx, const char *str)
> >  {
> >      IOWriterContext *ctx = wctx->priv;
> > -    avio_write(ctx->avio_context, str, strlen(str));
> > +    avio_write(ctx->avio_context, (const unsigned char *)str,
> (int)strlen(str));
> >  }
> >
> >  static void io_printf(AVTextWriterContext *wctx, const char *fmt,
> ...)
> > @@ -78,10 +78,12 @@ const AVTextWriter avtextwriter_avio = {
> >
> >  int avtextwriter_create_file(AVTextWriterContext **pwctx, const
> char *output_filename)
> >  {
> 
> > +    if (!output_filename || !output_filename[0])
> > +        return AVERROR(EINVAL);
> 
> I'd add a warning to aid debugging.

Right!


> >      IOWriterContext *ctx;
> >      int ret;
> >
> > -
> >      ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
> >      if (ret < 0)
> >          return ret;
> > @@ -103,6 +105,9 @@ int avtextwriter_create_file(AVTextWriterContext
> **pwctx, const char *output_fil
> >
> >  int avtextwriter_create_avio(AVTextWriterContext **pwctx,
> AVIOContext *avio_ctx, int close_on_uninit)
> >  {
> 
> > +    if (!pwctx || !avio_ctx)
> > +        return AVERROR(EINVAL);
> > +
> 
> maybe warning in this case as well

Makes sense!

> 
> [...]
> 
> Looks good to me otherwise.


Thank you!
sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use ac_default_item_name
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use ac_default_item_name softworkz
@ 2025-04-21 17:31         ` Stefano Sabatini
  2025-04-22 21:10           ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-21 17:31 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Sunday 2025-04-20 22:59:07 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/tf_internal.h | 6 +-----
>  1 file changed, 1 insertion(+), 5 deletions(-)

Typo in commit headling: ac_default... -> av_default...

> 
> diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
> index 7b326328cb..e145bc83bb 100644
> --- a/fftools/textformat/tf_internal.h
> +++ b/fftools/textformat/tf_internal.h
> @@ -29,13 +29,9 @@
>  #include "avtextformat.h"
>  
>  #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,                  \
> +    .item_name  = av_default_item_name,             \
>      .option     = name##_options                    \
>  }

Looks good to me.
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 03/11] fftools/textformat: Introduce common header and deduplicate code
  2025-04-21 17:28         ` Stefano Sabatini
@ 2025-04-21 17:31           ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-21 17:31 UTC (permalink / raw)
  To: Stefano Sabatini, FFmpeg development discussions and patches



> -----Original Message-----
> From: Stefano Sabatini <stefasab@gmail.com>
> Sent: Montag, 21. April 2025 19:29
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Cc: softworkz <softworkz@hotmail.com>
> Subject: Re: [FFmpeg-devel] [PATCH v4 03/11] fftools/textformat:
> Introduce common header and deduplicate code
> 
> On date Sunday 2025-04-20 22:59:06 +0000, softworkz wrote:
> > From: softworkz <softworkz@hotmail.com>
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> 
> first part should be good
> 
> [...]
> 
> 
> > diff --git a/fftools/textformat/tw_avio.c
> b/fftools/textformat/tw_avio.c
> > index d1b494b7b4..48868ebf5d 100644
> > --- a/fftools/textformat/tw_avio.c
> > +++ b/fftools/textformat/tw_avio.c
> > @@ -56,14 +56,11 @@ static void io_put_str(AVTextWriterContext
> *wctx, const char *str)
> >      avio_write(ctx->avio_context, (const unsigned char *)str,
> (int)strlen(str));
> >  }
> >
> > -static void io_printf(AVTextWriterContext *wctx, const char *fmt,
> ...)
> > +static void io_printf(AVTextWriterContext *wctx, const char *fmt,
> va_list vl)
> >  {
> >      IOWriterContext *ctx = wctx->priv;
> > -    va_list ap;
> >
> > -    va_start(ap, fmt);
> > -    avio_vprintf(ctx->avio_context, fmt, ap);
> > -    va_end(ap);
> > +    avio_vprintf(ctx->avio_context, fmt, vl);
> >  }
> >
> >
> > diff --git a/fftools/textformat/tw_buffer.c
> b/fftools/textformat/tw_buffer.c
> > index f8b38414a6..f861722247 100644
> > --- a/fftools/textformat/tw_buffer.c
> > +++ b/fftools/textformat/tw_buffer.c
> > @@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext
> *wctx, const char *str)
> >      av_bprintf(ctx->buffer, "%s", str);
> >  }
> >
> > -static void buffer_printf(AVTextWriterContext *wctx, const char
> *fmt, ...)
> > +static void buffer_printf(AVTextWriterContext *wctx, const char
> *fmt, va_list vl)
> >  {
> >      BufferWriterContext *ctx = wctx->priv;
> >
> > -    va_list vargs;
> > -    va_start(vargs, fmt);
> > -    av_vbprintf(ctx->buffer, fmt, vargs);
> > -    va_end(vargs);
> > +    av_vbprintf(ctx->buffer, fmt, vl);
> >  }
> >
> >
> > diff --git a/fftools/textformat/tw_stdout.c
> b/fftools/textformat/tw_stdout.c
> > index 23de6f671f..dace55f38a 100644
> > --- a/fftools/textformat/tw_stdout.c
> > +++ b/fftools/textformat/tw_stdout.c
> > @@ -53,13 +53,9 @@ static inline void
> stdout_put_str(AVTextWriterContext *wctx, const char *str)
> >      printf("%s", str);
> >  }
> >
> > -static inline void stdout_printf(AVTextWriterContext *wctx, const
> char *fmt, ...)
> > +static inline void stdout_printf(AVTextWriterContext *wctx, const
> char *fmt, va_list vl)
> >  {
> > -    va_list ap;
> > -
> > -    va_start(ap, fmt);
> > -    vprintf(fmt, ap);
> > -    va_end(ap);
> > +    vprintf(fmt, vl);
> >  }
> 
> This looks like a logically different change and I'd rather move to a
> dedicated commit.

I'm always unsure which changes to separate and which ones to squash 😊

Will do!
Thanks


_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat: Quality improvements
  2025-04-21 17:21           ` Nicolas George
@ 2025-04-21 17:40             ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-21 17:40 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Nicolas George
> Sent: Montag, 21. April 2025 19:21
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat:
> Quality improvements
> 
> Stefano Sabatini (HE12025-04-21):
> > > +    if (!pwctx || !avio_ctx)
> > > +        return AVERROR(EINVAL);
> > > +
> > maybe warning in this case as well
> 
> Checking that an argument that must not be null is not null is the
> responsibility of the caller: crashing here is the right thing to do,
> same as fprintf(NULL, "fmt").
> 
> Therefore, an assertion is the best choice.
> 
> Regards,
> 
> --
>   Nicolas George
> _______________________________________________

Okay, I'll do that. 

Thank you.
sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use ac_default_item_name
  2025-04-21 17:31         ` Stefano Sabatini
@ 2025-04-22 21:10           ` softworkz .
  2025-04-23 22:36             ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: softworkz . @ 2025-04-22 21:10 UTC (permalink / raw)
  To: Stefano Sabatini, FFmpeg development discussions and patches



> -----Original Message-----
> From: Stefano Sabatini <stefasab@gmail.com>
> Sent: Montag, 21. April 2025 19:31
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Cc: softworkz <softworkz@hotmail.com>
> Subject: Re: [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use
> ac_default_item_name
> 
> On date Sunday 2025-04-20 22:59:07 +0000, softworkz wrote:
> > From: softworkz <softworkz@hotmail.com>
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  fftools/textformat/tf_internal.h | 6 +-----
> >  1 file changed, 1 insertion(+), 5 deletions(-)
> 
> Typo in commit headling: ac_default... -> av_default...
> 
> >
> > diff --git a/fftools/textformat/tf_internal.h
> b/fftools/textformat/tf_internal.h
> > index 7b326328cb..e145bc83bb 100644
> > --- a/fftools/textformat/tf_internal.h
> > +++ b/fftools/textformat/tf_internal.h
> > @@ -29,13 +29,9 @@
> >  #include "avtextformat.h"
> >
> >  #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,                  \
> > +    .item_name  = av_default_item_name,             \
> >      .option     = name##_options                    \
> >  }
> 
> Looks good to me.


Hi Stefano,

thanks a lot for the review.

I have applied all the suggested changes (including those where I didn't
explicitly say I would).

For the avtext_context_open() function, I have introduced a new structure
AVTextFormatOptions to achieve a stable function signature that doesn't
need to be changed when adding new options.
This is done in a separate commit. The other two changes where you mentioned
have been moved into their own commits as well.

(V5 patchset coming)

Thanks again,
sw


_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing
  2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
                         ` (10 preceding siblings ...)
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 11/11] fftools/graphprint: Now, make it a Killer-Feature! softworkz
@ 2025-04-22 21:55       ` ffmpegagent
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 01/14] fftools/textformat: Formatting and whitespace changes softworkz
                           ` (14 more replies)
  11 siblings, 15 replies; 130+ messages in thread
From: ffmpegagent @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

Shortest cover letter for my longest-running FFmpeg patchset:

 * Apply
 * Build
 * Add the "-sg" switch to any FFmpeg command line
 * Press 'q' when you don't want to wait

SG = Show Graph

Documentation and examples can be found here:

https://github.com/softworkz/ffmpeg_output_apis/wiki


Version Updates
===============


V2
==

 * Rebased on top of Andreas' improvements
 * Applied changes from review (thanks, Andreas)


V3
==

 * Fixed all "new warnings"
 * Fixed out-of-tree building (thanks, Michael)


V4
==

 * Resolved merge conflict
 * Fixed build on MinGW (missing include due to WIN32_LEAN_AND_MEAN being
   defined) (thanks, Michael)


V5
==

 * Applied changes as per review from Stefano (thanks!)
 * Introduced AVTextFormatOptions struct for options in
   avtext_context_open()

.

softworkz (14):
  fftools/textformat: Formatting and whitespace changes
  fftools/textformat: Apply quality improvements
  fftools/avtextformat: Re-use BPrint in loop
  fftools/textformat: Introduce AVTextFormatOptions for
    avtext_context_open()
  fftools/textformat: Introduce common header and deduplicate code
  fftools/textformat: AVTextWriter change writer_printf signature
  fftools/tf_internal: Use av_default_item_name
  fftools/textformat: Add function avtext_print_integer_flags()
  fftools/ffmpeg_filter: Move some declaration to new header file
  avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  fftools/resources: Add resource manager files
  fftools/ffmpeg_mux: Make ms_from_ost() inline
  fftools/graphprint: Add execution graph printing
  fftools/graphprint: Now, make it a Killer-Feature!

 doc/APIchanges                     |    3 +
 doc/ffmpeg.texi                    |   14 +
 ffbuild/common.mak                 |   28 +-
 fftools/Makefile                   |   22 +-
 fftools/ffmpeg.c                   |    4 +
 fftools/ffmpeg.h                   |    4 +
 fftools/ffmpeg_filter.c            |  195 +----
 fftools/ffmpeg_filter.h            |  234 ++++++
 fftools/ffmpeg_mux.h               |    2 +-
 fftools/ffmpeg_opt.c               |   17 +
 fftools/ffprobe.c                  |   13 +-
 fftools/graph/filelauncher.c       |  205 +++++
 fftools/graph/graphprint.c         | 1147 ++++++++++++++++++++++++++++
 fftools/graph/graphprint.h         |   62 ++
 fftools/resources/.gitignore       |    4 +
 fftools/resources/Makefile         |   27 +
 fftools/resources/graph.css        |  353 +++++++++
 fftools/resources/graph.html       |   86 +++
 fftools/resources/resman.c         |  213 ++++++
 fftools/resources/resman.h         |   50 ++
 fftools/textformat/avtextformat.c  |  229 +++---
 fftools/textformat/avtextformat.h  |   73 +-
 fftools/textformat/avtextwriters.h |   11 +-
 fftools/textformat/tf_compact.c    |  121 +--
 fftools/textformat/tf_default.c    |   55 +-
 fftools/textformat/tf_flat.c       |   51 +-
 fftools/textformat/tf_ini.c        |   62 +-
 fftools/textformat/tf_internal.h   |   81 ++
 fftools/textformat/tf_json.c       |   56 +-
 fftools/textformat/tf_mermaid.c    |  658 ++++++++++++++++
 fftools/textformat/tf_mermaid.h    |   41 +
 fftools/textformat/tf_xml.c        |   68 +-
 fftools/textformat/tw_avio.c       |   18 +-
 fftools/textformat/tw_buffer.c     |    7 +-
 fftools/textformat/tw_stdout.c     |    8 +-
 libavfilter/avfilter.c             |    9 +
 libavfilter/avfilter.h             |   12 +
 37 files changed, 3686 insertions(+), 557 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h
 create mode 100644 fftools/graph/filelauncher.c
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h
 create mode 100644 fftools/textformat/tf_internal.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h


base-commit: 9e1162bdf1454d7ae3737429bcc6bd66e5da303a
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-66%2Fsoftworkz%2Fsubmit_print_execution_graph-v5
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-66/softworkz/submit_print_execution_graph-v5
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/66

Range-diff vs v4:

  1:  dbd9193a0d !  1:  0672fc41e7 fftools/textformat: Formatting and whitespace changes
     @@ fftools/textformat/avtextformat.c: static const char *textcontext_get_formatter_
       };
       
      @@ fftools/textformat/avtextformat.c: void avtext_context_close(AVTextFormatContext **ptctx)
     - }
       
       
     --int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
     + 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)
     -+int avtext_context_open(AVTextFormatContext      **ptctx,
     -+                        const AVTextFormatter     *formatter,
     -+                        AVTextWriterContext       *writer_context,
     -+                        const char                *args,
     -+                        const 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;
     ++                        const AVTextFormatSection *sections, int nb_sections,
     +                         int show_value_unit,
     +                         int use_value_prefix,
     +                         int use_byte_value_binary_prefix,
      @@ fftools/textformat/avtextformat.c: int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
               av_dict_free(&opts);
           }
  2:  1516e67a5b !  2:  1e312f4685 fftools/textformat: Quality improvements
     @@ Metadata
      Author: softworkz <softworkz@hotmail.com>
      
       ## Commit message ##
     -    fftools/textformat: Quality improvements
     +    fftools/textformat: Apply quality improvements
     +
     +    Perform multiple improvements to increase code robustness.
     +    In particular:
     +    - favor unsigned counters for loops
     +    - add missing checks
     +    - avoid possibly leaks
     +    - move variable declarations to inner scopes when feasible
     +    - provide explicit type-casting when needed
      
          Signed-off-by: softworkz <softworkz@hotmail.com>
      
     @@ fftools/textformat/avtextformat.c: static const AVClass textcontext_class = {
               av_bprintf(bp, "%02X", ubuf[i]);
       }
       
     -@@ fftools/textformat/avtextformat.c: int avtext_context_open(AVTextFormatContext      **ptctx,
     +@@ fftools/textformat/avtextformat.c: int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
           AVTextFormatContext *tctx;
           int i, ret = 0;
       
     --    if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
      +    if (!ptctx || !formatter)
      +        return AVERROR(EINVAL);
      +
     -+    if (!((tctx = av_mallocz(sizeof(AVTextFormatContext))))) {
     +     if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
               ret = AVERROR(ENOMEM);
               goto fail;
     -     }
     -@@ fftools/textformat/avtextformat.c: int avtext_context_open(AVTextFormatContext      **ptctx,
     +@@ fftools/textformat/avtextformat.c: int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
                           av_log(NULL, AV_LOG_ERROR, " %s", n);
                       av_log(NULL, AV_LOG_ERROR, "\n");
                   }
     @@ fftools/textformat/avtextformat.c: void avtext_print_section_footer(AVTextFormat
      -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
      +    const AVTextFormatSection *section;
      +
     -+    if (!key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
     -+        return;
     ++    av_assert0(key && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
      +
      +    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);
     -@@ fftools/textformat/avtextformat.c: void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
     - 
     - static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
     - {
     --    const uint8_t *p, *endp;
     -+    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
     -     AVBPrint dstbuf;
     -+    AVBPrint bp;
     -     int invalid_chars_nb = 0, ret = 0;
     - 
     -+    *dstp = NULL;
     -     av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
     -+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     - 
     --    endp = src + strlen(src);
     --    for (p = src; *p;) {
     --        uint32_t code;
     -+    endp = srcp + strlen(src);
     -+    for (p = srcp; *p;) {
     -+        int32_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);
     -+            av_bprint_clear(&bp);
     -+            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;
     -         }
     - 
     -@@ fftools/textformat/avtextformat.c: static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
     -         }
     - 
     -         if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
     --            av_bprint_append_data(&dstbuf, p0, p-p0);
     -+            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
     -     }
     - 
     -     if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
     -@@ fftools/textformat/avtextformat.c: static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
     - 
     - end:
     -     av_bprint_finalize(&dstbuf, dstp);
     -+    av_bprint_finalize(&bp, NULL);
     -     return ret;
     - }
     - 
      @@ fftools/textformat/avtextformat.c: struct unit_value {
           const char *unit;
       };
     @@ fftools/textformat/avtextformat.c: void avtext_print_unit_int(AVTextFormatContex
      +    const AVTextFormatSection *section;
           int ret = 0;
       
     -+    if (!key || !val || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
     -+        return AVERROR(EINVAL);
     ++    av_assert0(key && val && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
      +
      +    section = tctx->section[tctx->level];
      +
     @@ fftools/textformat/tf_json.c: static const char *json_escape_str(AVBPrint *dst,
           const char *p;
       
      +    if (!src) {
     -+        av_log(log_ctx, AV_LOG_ERROR, "json_escape_str: NULL source string\n");
     ++        av_log(log_ctx, AV_LOG_WARNING, "Cannot escape NULL string, returning NULL\n");
      +        return NULL;
      +    }
      +
     @@ fftools/textformat/tf_json.c: static const char *json_escape_str(AVBPrint *dst,
                   av_bprint_chars(dst, *p, 1);
               }
      @@ fftools/textformat/tf_json.c: static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
     -         wctx->section[wctx->level-1] : NULL;
     - 
     -     if (wctx->level && wctx->nb_item[wctx->level-1])
     + {
     +     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;
     ++    const AVTextFormatSection *section = wctx->section[wctx->level];
     ++    const AVTextFormatSection *parent_section = wctx->level ? wctx->section[wctx->level - 1] : NULL;
     + 
     +-    if (wctx->level && wctx->nb_item[wctx->level-1])
      +    if (wctx->level && wctx->nb_item[wctx->level - 1])
               writer_put_str(wctx, ",\n");
       
           if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
     +@@ fftools/textformat/tf_json.c: static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
     + 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;
     ++    const 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))
      
       ## fftools/textformat/tf_xml.c ##
      @@
     @@ fftools/textformat/tf_xml.c
       #include "avtextformat.h"
      
       ## fftools/textformat/tw_avio.c ##
     +@@
     + #include <string.h>
     + 
     + #include "avtextwriters.h"
     ++#include "libavutil/avassert.h"
     + 
     + #include "libavutil/error.h"
     + 
      @@ fftools/textformat/tw_avio.c: static void io_w8(AVTextWriterContext *wctx, int b)
       static void io_put_str(AVTextWriterContext *wctx, const char *str)
       {
     @@ fftools/textformat/tw_avio.c: const AVTextWriter avtextwriter_avio = {
       
       int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
       {
     -+    if (!output_filename || !output_filename[0])
     ++    if (!output_filename || !output_filename[0]) {
     ++        av_log(NULL, AV_LOG_ERROR, "The output_filename cannot be NULL or empty\n");
      +        return AVERROR(EINVAL);
     ++    }
      +
           IOWriterContext *ctx;
           int ret;
     @@ fftools/textformat/tw_avio.c: int avtextwriter_create_file(AVTextWriterContext *
       
       int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
       {
     -+    if (!pwctx || !avio_ctx)
     -+        return AVERROR(EINVAL);
     ++    av_assert0(avio_ctx);
      +
           IOWriterContext *ctx;
           int ret;
  -:  ---------- >  3:  c71836fce0 fftools/avtextformat: Re-use BPrint in loop
  -:  ---------- >  4:  26be409371 fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open()
  3:  7024548b92 !  5:  89da2c883e fftools/textformat: Introduce common header and deduplicate code
     @@ Commit message
      
          Signed-off-by: softworkz <softworkz@hotmail.com>
      
     - ## fftools/textformat/avtextwriters.h ##
     -@@ fftools/textformat/avtextwriters.h: typedef struct AVTextWriter {
     -     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, ...);
     -+    void (*writer_printf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
     - } AVTextWriter;
     - 
     - typedef struct AVTextWriterContext {
     -
       ## fftools/textformat/tf_compact.c ##
      @@
       #include "libavutil/bprint.h"
     @@ fftools/textformat/tf_json.c: static const char *json_escape_str(AVBPrint *dst,
      +    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
           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])
     +-    const AVTextFormatSection *section = wctx->section[wctx->level];
     +-    const AVTextFormatSection *parent_section = wctx->level ? wctx->section[wctx->level - 1] : NULL;
     ++
      +    if (!section)
      +        return;
     -+
     + 
           if (wctx->level && wctx->nb_item[wctx->level - 1])
               writer_put_str(wctx, ",\n");
     - 
      @@ fftools/textformat/tf_json.c: static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
       
       static void json_print_section_footer(AVTextFormatContext *wctx)
     @@ fftools/textformat/tf_json.c: static void json_print_str(AVTextFormatContext *wc
       {
      +    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
           JSONContext *json = wctx->priv;
     --    const struct AVTextFormatSection *parent_section = wctx->level ?
     --        wctx->section[wctx->level-1] : NULL;
     +-    const 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))
     @@ fftools/textformat/tf_xml.c: const AVTextFormatter avtextformatter_xml = {
           .priv_class           = &xml_class,
       };
      -
     -
     - ## fftools/textformat/tw_avio.c ##
     -@@ fftools/textformat/tw_avio.c: static void io_put_str(AVTextWriterContext *wctx, const char *str)
     -     avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
     - }
     - 
     --static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
     -+static void io_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
     - {
     -     IOWriterContext *ctx = wctx->priv;
     --    va_list ap;
     - 
     --    va_start(ap, fmt);
     --    avio_vprintf(ctx->avio_context, fmt, ap);
     --    va_end(ap);
     -+    avio_vprintf(ctx->avio_context, fmt, vl);
     - }
     - 
     - 
     -
     - ## fftools/textformat/tw_buffer.c ##
     -@@ fftools/textformat/tw_buffer.c: static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
     -     av_bprintf(ctx->buffer, "%s", str);
     - }
     - 
     --static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
     -+static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
     - {
     -     BufferWriterContext *ctx = wctx->priv;
     - 
     --    va_list vargs;
     --    va_start(vargs, fmt);
     --    av_vbprintf(ctx->buffer, fmt, vargs);
     --    va_end(vargs);
     -+    av_vbprintf(ctx->buffer, fmt, vl);
     - }
     - 
     - 
     -
     - ## fftools/textformat/tw_stdout.c ##
     -@@ fftools/textformat/tw_stdout.c: static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
     -     printf("%s", str);
     - }
     - 
     --static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
     -+static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
     - {
     --    va_list ap;
     --
     --    va_start(ap, fmt);
     --    vprintf(fmt, ap);
     --    va_end(ap);
     -+    vprintf(fmt, vl);
     - }
     - 
     - 
  -:  ---------- >  6:  ecf6f061b2 fftools/textformat: AVTextWriter change writer_printf signature
  4:  4f1218b594 !  7:  c190f79565 fftools/tf_internal: Use ac_default_item_name
     @@ Metadata
      Author: softworkz <softworkz@hotmail.com>
      
       ## Commit message ##
     -    fftools/tf_internal: Use ac_default_item_name
     +    fftools/tf_internal: Use av_default_item_name
      
          Signed-off-by: softworkz <softworkz@hotmail.com>
      
  5:  c1fea3027a =  8:  1fe4a8fe6c fftools/textformat: Add function avtext_print_integer_flags()
  6:  28aeb7180d =  9:  ba034ef3b1 fftools/ffmpeg_filter: Move some declaration to new header file
  7:  d2ad11ac85 = 10:  6e31aa603a avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  8:  9fa93efdda = 11:  ea2d41b048 fftools/resources: Add resource manager files
  9:  45b2a38592 = 12:  4fa179848a fftools/ffmpeg_mux: Make ms_from_ost() inline
 10:  0e52640aca ! 13:  62d4cab294 fftools/graphprint: Add execution graph printing
     @@ fftools/graph/graphprint.c (new)
      +        goto fail;
      +    }
      +
     -+    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), 0, 0, 0, 0, -1, NULL);
     ++    AVTextFormatOptions tf_options = { .show_optional_fields = -1 };
     ++    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), tf_options, NULL);
      +    if (ret < 0) {
      +        goto fail;
      +    }
 11:  07a8d731ee = 14:  35fc23039e fftools/graphprint: Now, make it a Killer-Feature!

-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 01/14] fftools/textformat: Formatting and whitespace changes
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
@ 2025-04-22 21:55         ` softworkz
  2025-04-23 22:08           ` Stefano Sabatini
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements softworkz
                           ` (13 subsequent siblings)
  14 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c  | 86 ++++++++++++++--------------
 fftools/textformat/avtextformat.h  | 16 +++---
 fftools/textformat/avtextwriters.h | 11 ++--
 fftools/textformat/tf_compact.c    | 91 +++++++++++++++++-------------
 fftools/textformat/tf_default.c    | 20 +++----
 fftools/textformat/tf_flat.c       | 26 +++++----
 fftools/textformat/tf_ini.c        | 36 ++++++------
 fftools/textformat/tf_json.c       | 10 ++--
 fftools/textformat/tf_xml.c        | 30 +++++-----
 9 files changed, 172 insertions(+), 154 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 9200b9b1ad..74d179c516 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -34,9 +34,9 @@
 #include "libavutil/opt.h"
 #include "avtextformat.h"
 
-#define SECTION_ID_NONE -1
+#define SECTION_ID_NONE (-1)
 
-#define SHOW_OPTIONAL_FIELDS_AUTO       -1
+#define SHOW_OPTIONAL_FIELDS_AUTO      (-1)
 #define SHOW_OPTIONAL_FIELDS_NEVER       0
 #define SHOW_OPTIONAL_FIELDS_ALWAYS      1
 
@@ -64,14 +64,14 @@ static const char *textcontext_get_formatter_name(void *p)
 
 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" },
+      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"}},
+      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 }
 };
 
@@ -126,7 +126,7 @@ void avtext_context_close(AVTextFormatContext **ptctx)
 
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
@@ -200,7 +200,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash) {
+    if (show_data_hash)
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -211,7 +211,6 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             return ret;
         }
-    }
 
     /* validate replace string */
     {
@@ -224,7 +223,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             if (ret < 0) {
                 AVBPrint bp;
                 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
-                bprint_bytes(&bp, p0, p-p0),
+                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);
@@ -248,15 +247,13 @@ fail:
 }
 
 /* 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_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)
+void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
@@ -272,8 +269,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx,
 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;
+    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]++;
@@ -285,8 +283,7 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
     tctx->level--;
 }
 
-void avtext_print_integer(AVTextFormatContext *tctx,
-                                        const char *key, int64_t val)
+void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
     const struct AVTextFormatSection *section = tctx->section[tctx->level];
 
@@ -324,11 +321,9 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
             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);
+                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);
@@ -340,11 +335,10 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
             av_bprint_append_data(&dstbuf, p0, p-p0);
     }
 
-    if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
+    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);
@@ -352,7 +346,11 @@ end:
 }
 
 struct unit_value {
-    union { double d; int64_t i; } val;
+    union {
+        double  d;
+        int64_t i;
+    } val;
+
     const char *unit;
 };
 
@@ -402,8 +400,9 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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 : "");
+                    prefix_string, tctx->show_value_unit ? uv.unit : "");
     }
 
     return buf;
@@ -427,8 +426,8 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 
     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)))
+            && (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)) {
@@ -440,11 +439,10 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
             if (ret < 0) goto end;
             tctx->formatter->print_string(tctx, key1, val1);
         end:
-            if (ret < 0) {
+            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 {
@@ -457,8 +455,7 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
     return ret;
 }
 
-void avtext_print_rational(AVTextFormatContext *tctx,
-                                         const char *key, AVRational q, char sep)
+void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep)
 {
     char buf[44];
     snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
@@ -466,7 +463,7 @@ void avtext_print_rational(AVTextFormatContext *tctx,
 }
 
 void avtext_print_time(AVTextFormatContext *tctx, const char *key,
-                              int64_t ts, const AVRational *time_base, int is_duration)
+                       int64_t ts, const AVRational *time_base, int is_duration)
 {
     char buf[128];
 
@@ -484,15 +481,14 @@ void avtext_print_time(AVTextFormatContext *tctx, const char *key,
 
 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)) {
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0))
         avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
-    } else {
+    else
         avtext_print_integer(tctx, key, ts);
-    }
 }
 
 void avtext_print_data(AVTextFormatContext *tctx, const char *name,
-                              const uint8_t *data, int size)
+                       const uint8_t *data, int size)
 {
     AVBPrint bp;
     int offset = 0, l, i;
@@ -520,12 +516,13 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 }
 
 void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
-                                   const uint8_t *data, int size)
+                            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));
@@ -551,7 +548,7 @@ void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
             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 --;
+            size--;
         }
         av_bprintf(&bp, "\n");
         offset += offset_add;
@@ -641,7 +638,8 @@ fail:
     return ret;
 }
 
-static const AVTextFormatter *registered_formatters[7+1];
+static const AVTextFormatter *registered_formatters[10 + 1];
+
 static void formatters_register_all(void)
 {
     static int initialized;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c2c56dc1a7..c598af3450 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -86,17 +86,17 @@ typedef struct AVTextFormatter {
 #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
+    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
+    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
+    const AVTextFormatSection *sections; ///< array containing all sections
+    int nb_sections;                   ///< number of sections
 
-    int level;                      ///< current level, starting from 0
+    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];
diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index c99d6b3548..34db3f1832 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -37,11 +37,11 @@ typedef struct AVTextWriter {
     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, ...);
+    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 {
@@ -49,7 +49,6 @@ typedef struct AVTextWriterContext {
     const AVTextWriter *writer;
     const char *name;
     void *priv;                     ///< private data for use by the writer
-
 } AVTextWriterContext;
 
 
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index 31bfc81513..d4ac296a42 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -58,10 +58,10 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 
     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 '\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)
@@ -78,6 +78,7 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 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)
@@ -114,16 +115,16 @@ typedef struct 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},
+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);
@@ -139,10 +140,13 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
     }
     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 {
+    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);
     }
@@ -162,8 +166,8 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
     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))))) {
+            (!(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 */
@@ -171,10 +175,10 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
         AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
 
         compact->nested_section[wctx->level] = 1;
-        compact->has_nested_elems[wctx->level-1] = 1;
+        compact->has_nested_elems[wctx->level - 1] = 1;
 
         av_bprintf(section_pbuf, "%s%s",
-                   wctx->section_pbuf[wctx->level-1].str, element_name);
+                   wctx->section_pbuf[wctx->level - 1].str, element_name);
 
         if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
             // add /TYPE to prefix
@@ -185,30 +189,33 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
                 char c =
                     (*p >= '0' && *p <= '9') ||
                     (*p >= 'a' && *p <= 'z') ||
-                    (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
+                    (*p >= 'A' && *p <= 'Z')
+                    ? (char)(char)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];
+        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])
+        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)))
+            !(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)
 {
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     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)))
+        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
         writer_w8(wctx, '\n');
 }
 
@@ -217,9 +224,12 @@ static void compact_print_str(AVTextFormatContext *wctx, const char *key, const
     CompactContext *compact = wctx->priv;
     AVBPrint buf;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
@@ -229,9 +239,12 @@ static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_
 {
     CompactContext *compact = wctx->priv;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
 }
 
@@ -253,15 +266,15 @@ const AVTextFormatter avtextformatter_compact = {
 #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},
+    { "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);
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 86582829e4..2c5047eafd 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -56,11 +56,11 @@ typedef struct DefaultContext {
 #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},
+    { "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);
@@ -69,7 +69,7 @@ DEFINE_FORMATTER_CLASS(default);
 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++)
+    for (i = 0; src[i] && i < dst_size - 1; i++)
         dst[i] = av_toupper(src[i]);
     dst[i] = 0;
     return dst;
@@ -85,10 +85,10 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 
     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))) {
+        !(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,
+                   wctx->section_pbuf[wctx->level - 1].str,
                    upcase_string(buf, sizeof(buf),
                                  av_x_if_null(section->element_name, section->name)));
     }
@@ -96,7 +96,7 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
     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)))
+    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));
 }
 
@@ -109,7 +109,7 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     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)))
+    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));
 }
 
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index 919d44bc6b..f692971bcc 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -57,12 +57,12 @@ typedef struct 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},
+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);
@@ -126,16 +126,18 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
     av_bprint_clear(buf);
     if (!parent_section)
         return;
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+
+    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))) {
+        !(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];
+            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);
         }
     }
@@ -166,6 +168,6 @@ const AVTextFormatter avtextformatter_flat = {
     .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,
+    .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
index d8099ff92e..88add0819a 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -64,9 +64,9 @@ typedef struct INIContext {
 #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},
+    { "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);
@@ -74,9 +74,9 @@ DEFINE_FORMATTER_CLASS(ini);
 static char *ini_escape_str(AVBPrint *dst, const char *src)
 {
     int i = 0;
-    char c = 0;
+    char c;
 
-    while (c = src[i++]) {
+    while ((c = src[i++])) {
         switch (c) {
         case '\b': av_bprintf(dst, "%s", "\\b"); break;
         case '\f': av_bprintf(dst, "%s", "\\f"); break;
@@ -84,9 +84,11 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
         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);
+        case '#':
+        case '=':
+        case ':':
+            av_bprint_chars(dst, '\\', 1);
+            /* fallthrough */
         default:
             if ((unsigned char)c < 32)
                 av_bprintf(dst, "\\x00%02x", c & 0xff);
@@ -112,23 +114,23 @@ static void ini_print_section_header(AVTextFormatContext *wctx, const void *data
         return;
     }
 
-    if (wctx->nb_item[wctx->level-1])
+    if (wctx->nb_item[wctx->level - 1])
         writer_w8(wctx, '\n');
 
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+    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))) {
+        !(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);
+            unsigned 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, ".%u", n);
         }
     }
 
-    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
         writer_printf(wctx, "[%s]\n", buf->str);
 }
 
@@ -154,6 +156,6 @@ const AVTextFormatter avtextformatter_ini = {
     .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,
+    .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
index c26a912435..b61d3740c6 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -56,9 +56,9 @@ typedef struct 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 },
+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 }
 };
 
@@ -76,8 +76,8 @@ static av_cold int json_init(AVTextFormatContext *wctx)
 
 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};
+    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++) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index 6c89d01e9d..befb39246d 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -58,11 +58,11 @@ typedef struct XMLContext {
 #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},
+    { "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);
@@ -104,8 +104,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 
         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 : "");
+                      xml->fully_qualified ? "ffprobe:" : "",
+                      xml->fully_qualified ? qual : "");
         return;
     }
 
@@ -115,12 +115,13 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
     }
 
     if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
-        wctx->level && wctx->nb_item[wctx->level-1])
+        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_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;
@@ -131,7 +132,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
         }
         writer_printf(wctx, ">\n", section->name);
     } else {
-        XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "<%s ", section->name);
         xml->within_tag = 1;
     }
 }
@@ -148,7 +150,8 @@ static void xml_print_section_footer(AVTextFormatContext *wctx)
         writer_put_str(wctx, "/>\n");
         xml->indent_level--;
     } else {
-        XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "</%s>\n", section->name);
         xml->indent_level--;
     }
 }
@@ -195,7 +198,8 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
     av_bprint_finalize(&buf, NULL);
 }
 
-static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
+static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
     xml_print_value(wctx, key, value, 0, 0);
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 01/14] fftools/textformat: Formatting and whitespace changes softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-23 22:34           ` Stefano Sabatini
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 03/14] fftools/avtextformat: Re-use BPrint in loop softworkz
                           ` (12 subsequent siblings)
  14 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Perform multiple improvements to increase code robustness.
In particular:
- favor unsigned counters for loops
- add missing checks
- avoid possibly leaks
- move variable declarations to inner scopes when feasible
- provide explicit type-casting when needed

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 85 ++++++++++++++++++++-----------
 fftools/textformat/avtextformat.h |  6 +--
 fftools/textformat/tf_default.c   |  8 ++-
 fftools/textformat/tf_ini.c       |  2 +-
 fftools/textformat/tf_json.c      | 17 ++++---
 fftools/textformat/tf_xml.c       |  3 --
 fftools/textformat/tw_avio.c      | 11 +++-
 7 files changed, 83 insertions(+), 49 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 74d179c516..1939a1f739 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
 
 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++)
+    for (unsigned i = 0; i < ubuf_size; i++)
         av_bprintf(bp, "%02X", ubuf[i]);
 }
 
@@ -137,6 +136,9 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
     AVTextFormatContext *tctx;
     int i, ret = 0;
 
+    if (!ptctx || !formatter)
+        return AVERROR(EINVAL);
+
     if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
@@ -209,25 +211,26 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
                     av_log(NULL, AV_LOG_ERROR, " %s", n);
                 av_log(NULL, AV_LOG_ERROR, "\n");
             }
-            return ret;
+            goto fail;
         }
 
     /* validate replace string */
     {
-        const uint8_t *p = tctx->string_validation_replacement;
-        const uint8_t *endp = p + strlen(p);
+        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
+        const uint8_t *endp = p + strlen((const char *)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);
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
                 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;
+                av_bprint_finalize(&bp, NULL);
+                goto fail;
             }
         }
     }
@@ -255,6 +258,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
 
 void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
+    if (section_id < 0 || section_id >= tctx->nb_sections)
+        return;
+
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
 
@@ -268,6 +274,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
 
 void avtext_print_section_footer(AVTextFormatContext *tctx)
 {
+    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
     int section_id = tctx->section[tctx->level]->id;
     int parent_section_id = tctx->level
         ? tctx->section[tctx->level - 1]->id
@@ -285,7 +294,11 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
+
+    av_assert0(key && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
+
+    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);
@@ -354,17 +367,18 @@ struct unit_value {
     const char *unit;
 };
 
-static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
+static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
 {
     double vald;
-    int64_t vali;
+    int64_t vali = 0;
     int show_float = 0;
 
     if (uv.unit == unit_second_str) {
         vald = uv.val.d;
         show_float = 1;
     } else {
-        vald = vali = uv.val.i;
+        vald = (double)uv.val.i;
+        vali = uv.val.i;
     }
 
     if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
@@ -383,17 +397,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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);
+                index = (int64_t)(log2(vald) / 10);
+                index = av_clip64(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);
+                index = (int64_t)(log10(vald) / 3);
+                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
                 vald /= si_prefixes[index].dec_val;
                 prefix_string = si_prefixes[index].dec_str;
             }
-            vali = vald;
+            vali = (int64_t)vald;
         }
 
         if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
@@ -421,9 +435,13 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
 
 int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
     int ret = 0;
 
+    av_assert0(key && val && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
+
+    section = tctx->section[tctx->level];
+
     if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
         (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
             && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
@@ -465,12 +483,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
 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);
+        char buf[128];
+        double d = av_q2d(*time_base) * ts;
         struct unit_value uv;
         uv.val.d = d;
         uv.unit = unit_second_str;
@@ -491,7 +508,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
                        const uint8_t *data, int size)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -518,25 +536,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 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 };
+    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
+    int len;
 
     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);
+    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
+    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
     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)
+                           uint8_t *data, int size, const char *format,
+                           int columns, int bytes, int offset_add)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
+
+    if (!name || !data || !format || columns <= 0 || bytes <= 0)
+        return;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -602,12 +624,15 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
     AVTextWriterContext *wctx;
     int ret = 0;
 
-    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
+    if (!pwctx || !writer)
+        return AVERROR(EINVAL);
+
+    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
-    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
+    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c598af3450..aea691f351 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -21,9 +21,7 @@
 #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"
@@ -103,7 +101,7 @@ struct AVTextFormatContext {
     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];
+    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
     AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
                                                   ///  used by various formatters
 
@@ -124,7 +122,7 @@ struct AVTextFormatContext {
 #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 2c5047eafd..ad97173b0b 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -68,9 +68,10 @@ 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;
+    unsigned i;
+
     for (i = 0; src[i] && i < dst_size - 1; i++)
-        dst[i] = av_toupper(src[i]);
+        dst[i] = (char)av_toupper(src[i]);
     dst[i] = 0;
     return dst;
 }
@@ -106,6 +107,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     const struct AVTextFormatSection *section = wctx->section[wctx->level];
     char buf[32];
 
+    if (!section)
+        return;
+
     if (def->noprint_wrappers || def->nested_section[wctx->level])
         return;
 
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index 88add0819a..dd77d0e8bf 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
             /* fallthrough */
         default:
             if ((unsigned char)c < 32)
-                av_bprintf(dst, "\\x00%02x", c & 0xff);
+                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
             else
                 av_bprint_chars(dst, c, 1);
             break;
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index b61d3740c6..50c3d90440 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
     static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
     const char *p;
 
+    if (!src) {
+        av_log(log_ctx, AV_LOG_WARNING, "Cannot escape NULL string, returning NULL\n");
+        return NULL;
+    }
+
     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);
+            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
         } else {
             av_bprint_chars(dst, *p, 1);
         }
@@ -100,11 +105,10 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *parent_section = wctx->level ? wctx->section[wctx->level - 1] : NULL;
 
-    if (wctx->level && wctx->nb_item[wctx->level-1])
+    if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
     if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
@@ -185,8 +189,7 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 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;
+    const 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))
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index befb39246d..28abfc6400 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -18,10 +18,7 @@
  * 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"
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
index 6034f74ec9..29889598bb 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -23,6 +23,7 @@
 #include <string.h>
 
 #include "avtextwriters.h"
+#include "libavutil/avassert.h"
 
 #include "libavutil/error.h"
 
@@ -53,7 +54,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
 static void io_put_str(AVTextWriterContext *wctx, const char *str)
 {
     IOWriterContext *ctx = wctx->priv;
-    avio_write(ctx->avio_context, str, strlen(str));
+    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
 static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
@@ -78,10 +79,14 @@ const AVTextWriter avtextwriter_avio = {
 
 int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
 {
+    if (!output_filename || !output_filename[0]) {
+        av_log(NULL, AV_LOG_ERROR, "The output_filename cannot be NULL or empty\n");
+        return AVERROR(EINVAL);
+    }
+
     IOWriterContext *ctx;
     int ret;
 
-
     ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
     if (ret < 0)
         return ret;
@@ -103,6 +108,8 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
 
 int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
 {
+    av_assert0(avio_ctx);
+
     IOWriterContext *ctx;
     int 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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 03/14] fftools/avtextformat: Re-use BPrint in loop
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 01/14] fftools/textformat: Formatting and whitespace changes softworkz
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-23 22:45           ` Stefano Sabatini
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 04/14] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
                           ` (11 subsequent siblings)
  14 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Instead of initializing a new BPrint in each iteration of
the loop, re-use the same BPrint struct and just clear it
for each iteration.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 1939a1f739..0a221f4a9a 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -308,24 +308,25 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
 
 static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
 {
-    const uint8_t *p, *endp;
+    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
     AVBPrint dstbuf;
+    AVBPrint bp;
     int invalid_chars_nb = 0, ret = 0;
 
+    *dstp = NULL;
     av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
 
-    endp = src + strlen(src);
-    for (p = src; *p;) {
-        uint32_t code;
+    endp = srcp + strlen(src);
+    for (p = srcp; *p;) {
+        int32_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);
+            av_bprint_clear(&bp);
+            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;
         }
 
@@ -345,7 +346,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
         }
 
         if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
-            av_bprint_append_data(&dstbuf, p0, p-p0);
+            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
     }
 
     if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
@@ -355,6 +356,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
 end:
     av_bprint_finalize(&dstbuf, dstp);
+    av_bprint_finalize(&bp, NULL);
     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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 04/14] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open()
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (2 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 03/14] fftools/avtextformat: Re-use BPrint in loop softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-23 22:48           ` Stefano Sabatini
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 05/14] fftools/textformat: Introduce common header and deduplicate code softworkz
                           ` (10 subsequent siblings)
  14 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This allows future addition of options without
changes to the signature of avtext_context_open().

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffprobe.c                 | 13 +++++++++----
 fftools/textformat/avtextformat.c | 21 ++++++++-------------
 fftools/textformat/avtextformat.h | 22 +++++++++++++++-------
 3 files changed, 32 insertions(+), 24 deletions(-)

diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index f5c83925b9..1277b1e4f9 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -3168,10 +3168,15 @@ int main(int argc, char **argv)
     if (ret < 0)
         goto end;
 
-    if ((ret = avtext_context_open(&tctx, f, wctx, f_args,
-                           sections, FF_ARRAY_ELEMS(sections), show_value_unit,
-                            use_value_prefix, use_byte_value_binary_prefix, use_value_sexagesimal_format,
-                            show_optional_fields, show_data_hash)) >= 0) {
+    AVTextFormatOptions tf_options = {
+        .show_optional_fields = show_optional_fields,
+        .show_value_unit = show_value_unit,
+        .use_value_prefix = use_value_prefix,
+        .use_byte_value_binary_prefix = use_byte_value_binary_prefix,
+        .use_value_sexagesimal_format = use_value_sexagesimal_format,
+    };
+
+    if ((ret = avtext_context_open(&tctx, f, wctx, f_args, sections, FF_ARRAY_ELEMS(sections), tf_options, show_data_hash)) >= 0) {
         if (f == &avtextformatter_xml)
             tctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;
 
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 0a221f4a9a..217d9da25e 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -125,13 +125,7 @@ void avtext_context_close(AVTextFormatContext **ptctx)
 
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const 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)
+                        const AVTextFormatSection *sections, int nb_sections, AVTextFormatOptions options, char *show_data_hash)
 {
     AVTextFormatContext *tctx;
     int i, ret = 0;
@@ -155,11 +149,11 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         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;
+    tctx->show_value_unit = options.show_value_unit;
+    tctx->use_value_prefix = options.use_value_prefix;
+    tctx->use_byte_value_binary_prefix = options.use_byte_value_binary_prefix;
+    tctx->use_value_sexagesimal_format = options.use_value_sexagesimal_format;
+    tctx->show_optional_fields = options.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);
@@ -202,7 +196,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash)
+    if (show_data_hash) {
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -213,6 +207,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             goto fail;
         }
+    }
 
     /* validate replace string */
     {
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index aea691f351..05a358132e 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -118,17 +118,25 @@ struct AVTextFormatContext {
     unsigned int string_validation_utf8_flags;
 };
 
+typedef struct AVTextFormatOptions {
+    int show_optional_fields;
+    int show_value_unit;
+    int use_value_prefix;
+    int use_byte_value_binary_prefix;
+    int use_value_sexagesimal_format;
+} AVTextFormatOptions;
+
 #define AV_TEXTFORMAT_PRINT_STRING_OPTIONAL 1
 #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
 
+#define AV_TEXTFORMAT_OPEN_SHOW_VALUE_UNIT               1
+#define AV_TEXTFORMAT_OPEN_USE_VALUE_PREFIX              2
+#define AV_TEXTFORMAT_OPEN_USE_BYTE_BINARY_PREFIX        4
+#define AV_TEXTFORMAT_OPEN_USE_VALUE_SEXAGESIMAL_FORMAT  8
+#define AV_TEXTFORMAT_OPEN_SHOW_OPTIONAL_FIELDS         16
+
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const 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);
+                        const AVTextFormatSection *sections, int nb_sections, AVTextFormatOptions options, char *show_data_hash);
 
 void avtext_context_close(AVTextFormatContext **tctx);
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 05/14] fftools/textformat: Introduce common header and deduplicate code
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (3 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 04/14] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-23 22:49           ` Stefano Sabatini
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 06/14] fftools/textformat: AVTextWriter change writer_printf signature softworkz
                           ` (9 subsequent siblings)
  14 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/tf_compact.c  | 32 +++++-------
 fftools/textformat/tf_default.c  | 27 +++-------
 fftools/textformat/tf_flat.c     | 25 +++-------
 fftools/textformat/tf_ini.c      | 24 +++------
 fftools/textformat/tf_internal.h | 85 ++++++++++++++++++++++++++++++++
 fftools/textformat/tf_json.c     | 35 +++++--------
 fftools/textformat/tf_xml.c      | 35 ++++++-------
 7 files changed, 142 insertions(+), 121 deletions(-)
 create mode 100644 fftools/textformat/tf_internal.h

diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index d4ac296a42..e52888239e 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -28,23 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 
 /* Compact output */
@@ -157,9 +141,12 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
+
     compact->terminate_line[wctx->level] = 1;
     compact->has_nested_elems[wctx->level] = 0;
 
@@ -210,8 +197,11 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
 
 static void compact_print_section_footer(AVTextFormatContext *wctx)
 {
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     CompactContext *compact = wctx->priv;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (!compact->nested_section[wctx->level] &&
         compact->terminate_line[wctx->level] &&
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index ad97173b0b..019bda9d44 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -27,21 +27,7 @@
 #include "avtextformat.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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -80,9 +66,11 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(&wctx->section_pbuf[wctx->level]);
     if (parent_section &&
@@ -104,7 +92,8 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 static void default_print_section_footer(AVTextFormatContext *wctx)
 {
     DefaultContext *def = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
     char buf[32];
 
     if (!section)
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index f692971bcc..d5517f109b 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -28,22 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
-
+#include "tf_internal.h"
 
 /* Flat output */
 
@@ -118,9 +103,11 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     /* build section header */
     av_bprint_clear(buf);
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index dd77d0e8bf..8959785295 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -28,21 +28,7 @@
 
 #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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -104,9 +90,11 @@ 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(buf);
     if (!parent_section) {
diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
new file mode 100644
index 0000000000..7b326328cb
--- /dev/null
+++ b/fftools/textformat/tf_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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
+ */
+
+/**
+ * @file
+ * Internal utilities for text formatters.
+ */
+
+#ifndef FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+#define FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+
+#include "avtextformat.h"
+
+#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                    \
+}
+
+
+/**
+ * Safely validate and access a section at a given level
+ */
+static inline const AVTextFormatSection *tf_get_section(AVTextFormatContext *tfc, int level)
+{
+    if (!tfc || level < 0 || level >= SECTION_MAX_NB_LEVELS || !tfc->section[level]) {
+        if (tfc)
+            av_log(tfc, AV_LOG_ERROR, "Invalid section access at level %d\n", level);
+        return NULL;
+    }
+    return tfc->section[level];
+}
+
+/**
+ * Safely access the parent section
+ */
+static inline const AVTextFormatSection *tf_get_parent_section(AVTextFormatContext *tfc, int level)
+{
+    if (level <= 0)
+        return NULL;
+
+    return tf_get_section(tfc, level - 1);
+}
+
+static inline void writer_w8(AVTextFormatContext *wctx, int b)
+{
+    wctx->writer->writer->writer_w8(wctx->writer, b);
+}
+
+static inline void writer_put_str(AVTextFormatContext *wctx, const char *str)
+{
+    wctx->writer->writer->writer_put_str(wctx->writer, str);
+}
+
+static inline void writer_printf(AVTextFormatContext *wctx, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    wctx->writer->writer->writer_printf(wctx->writer, fmt, args);
+    va_end(args);
+}
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_INTERNAL_H */
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index 50c3d90440..593d6c2947 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -27,22 +27,7 @@
 #include "avtextformat.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                    \
-}
-
+#include "tf_internal.h"
 
 /* JSON output */
 
@@ -103,10 +88,13 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
 
 static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
     AVBPrint buf;
-    const AVTextFormatSection *section = wctx->section[wctx->level];
-    const AVTextFormatSection *parent_section = wctx->level ? wctx->section[wctx->level - 1] : NULL;
+
+    if (!section)
+        return;
 
     if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
@@ -141,8 +129,11 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 
 static void json_print_section_footer(AVTextFormatContext *wctx)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         json->indent_level--;
@@ -175,9 +166,8 @@ static inline void json_print_item_str(AVTextFormatContext *wctx,
 
 static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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);
@@ -188,8 +178,8 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 
 static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const 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))
@@ -213,4 +203,3 @@ const AVTextFormatter avtextformatter_json = {
     .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
index 28abfc6400..6b09e09ab4 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -25,21 +25,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 /* XML output */
 
@@ -90,9 +76,11 @@ static av_cold int xml_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
@@ -138,7 +126,10 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 static void xml_print_section_footer(AVTextFormatContext *wctx)
 {
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
@@ -158,7 +149,10 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
 {
     AVBPrint buf;
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
 
@@ -216,4 +210,3 @@ const AVTextFormatter avtextformatter_xml = {
     .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
     .priv_class           = &xml_class,
 };
-
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 06/14] fftools/textformat: AVTextWriter change writer_printf signature
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (4 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 05/14] fftools/textformat: Introduce common header and deduplicate code softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-23 23:07           ` Stefano Sabatini
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 07/14] fftools/tf_internal: Use av_default_item_name softworkz
                           ` (8 subsequent siblings)
  14 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Using va_list provides greater flebility

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextwriters.h | 2 +-
 fftools/textformat/tw_avio.c       | 7 ++-----
 fftools/textformat/tw_buffer.c     | 7 ++-----
 fftools/textformat/tw_stdout.c     | 8 ++------
 4 files changed, 7 insertions(+), 17 deletions(-)

diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index 34db3f1832..fd6da747eb 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -41,7 +41,7 @@ typedef struct AVTextWriter {
     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, ...);
+    void (*writer_printf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
 } AVTextWriter;
 
 typedef struct AVTextWriterContext {
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
index 29889598bb..7d52dc4cf5 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -57,14 +57,11 @@ static void io_put_str(AVTextWriterContext *wctx, const char *str)
     avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
-static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void io_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     IOWriterContext *ctx = wctx->priv;
-    va_list ap;
 
-    va_start(ap, fmt);
-    avio_vprintf(ctx->avio_context, fmt, ap);
-    va_end(ap);
+    avio_vprintf(ctx->avio_context, fmt, vl);
 }
 
 
diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
index f8b38414a6..f861722247 100644
--- a/fftools/textformat/tw_buffer.c
+++ b/fftools/textformat/tw_buffer.c
@@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
     av_bprintf(ctx->buffer, "%s", str);
 }
 
-static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     BufferWriterContext *ctx = wctx->priv;
 
-    va_list vargs;
-    va_start(vargs, fmt);
-    av_vbprintf(ctx->buffer, fmt, vargs);
-    va_end(vargs);
+    av_vbprintf(ctx->buffer, fmt, vl);
 }
 
 
diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
index 23de6f671f..dace55f38a 100644
--- a/fftools/textformat/tw_stdout.c
+++ b/fftools/textformat/tw_stdout.c
@@ -53,13 +53,9 @@ static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
     printf("%s", str);
 }
 
-static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
-    va_list ap;
-
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
+    vprintf(fmt, vl);
 }
 
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 07/14] fftools/tf_internal: Use av_default_item_name
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (5 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 06/14] fftools/textformat: AVTextWriter change writer_printf signature softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-23 22:57           ` Stefano Sabatini
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 08/14] fftools/textformat: Add function avtext_print_integer_flags() softworkz
                           ` (7 subsequent siblings)
  14 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/tf_internal.h | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
index 7b326328cb..e145bc83bb 100644
--- a/fftools/textformat/tf_internal.h
+++ b/fftools/textformat/tf_internal.h
@@ -29,13 +29,9 @@
 #include "avtextformat.h"
 
 #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,                  \
+    .item_name  = av_default_item_name,             \
     .option     = name##_options                    \
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 08/14] fftools/textformat: Add function avtext_print_integer_flags()
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (6 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 07/14] fftools/tf_internal: Use av_default_item_name softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 09/14] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
                           ` (6 subsequent siblings)
  14 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This function works analog to the avtext_print_string() which already
has a flags parameter.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 21 +++++++++++++++++++++
 fftools/textformat/avtextformat.h |  2 ++
 2 files changed, 23 insertions(+)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 217d9da25e..4dae024814 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -301,6 +301,27 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
     }
 }
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags)
+{
+    const AVTextFormatSection *section;
+
+    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
+    section = tctx->section[tctx->level];
+
+    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;
+
+    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, *srcp = (const uint8_t *)src;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index 05a358132e..c97a477fa9 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -147,6 +147,8 @@ void avtext_print_section_footer(AVTextFormatContext *tctx);
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags);
+
 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);
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 09/14] fftools/ffmpeg_filter: Move some declaration to new header file
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (7 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 08/14] fftools/textformat: Add function avtext_print_integer_flags() softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 10/14] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
                           ` (5 subsequent siblings)
  14 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

to allow filtergraph printing to access the information.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_filter.c | 190 +-------------------------------
 fftools/ffmpeg_filter.h | 234 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+), 189 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index d314aec206..eab9487f97 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ffmpeg.h"
+#include "ffmpeg_filter.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -42,44 +43,6 @@
 // FIXME private header, used for mid_pred()
 #include "libavcodec/mathops.h"
 
-typedef struct FilterGraphPriv {
-    FilterGraph      fg;
-
-    // name used for logging
-    char             log_name[32];
-
-    int              is_simple;
-    // true when the filtergraph contains only meta filters
-    // that do not modify the frame data
-    int              is_meta;
-    // source filters are present in the graph
-    int              have_sources;
-    int              disable_conversions;
-
-    unsigned         nb_outputs_done;
-
-    const char      *graph_desc;
-
-    int              nb_threads;
-
-    // frame for temporarily holding output from the filtergraph
-    AVFrame         *frame;
-    // frame for sending output to the encoder
-    AVFrame         *frame_enc;
-
-    Scheduler       *sch;
-    unsigned         sch_idx;
-} FilterGraphPriv;
-
-static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
-{
-    return (FilterGraphPriv*)fg;
-}
-
-static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
-{
-    return (const FilterGraphPriv*)fg;
-}
 
 // data that is local to the filter thread and not visible outside of it
 typedef struct FilterGraphThread {
@@ -102,157 +65,6 @@ typedef struct FilterGraphThread {
     uint8_t         *eof_out;
 } FilterGraphThread;
 
-typedef struct InputFilterPriv {
-    InputFilter         ifilter;
-
-    InputFilterOptions  opts;
-
-    int                 index;
-
-    AVFilterContext    *filter;
-
-    // used to hold submitted input
-    AVFrame            *frame;
-
-    /* for filters that are not yet bound to an input stream,
-     * this stores the input linklabel, if any */
-    uint8_t            *linklabel;
-
-    // filter data type
-    enum AVMediaType    type;
-    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
-    // same as type otherwise
-    enum AVMediaType    type_src;
-
-    int                 eof;
-    int                 bound;
-    int                 drop_warned;
-    uint64_t            nb_dropped;
-
-    // parameters configured for this input
-    int                 format;
-
-    int                 width, height;
-    AVRational          sample_aspect_ratio;
-    enum AVColorSpace   color_space;
-    enum AVColorRange   color_range;
-
-    int                 sample_rate;
-    AVChannelLayout     ch_layout;
-
-    AVRational          time_base;
-
-    AVFrameSideData   **side_data;
-    int                 nb_side_data;
-
-    AVFifo             *frame_queue;
-
-    AVBufferRef        *hw_frames_ctx;
-
-    int                 displaymatrix_present;
-    int                 displaymatrix_applied;
-    int32_t             displaymatrix[9];
-
-    int                 downmixinfo_present;
-    AVDownmixInfo       downmixinfo;
-
-    struct {
-        AVFrame *frame;
-
-        int64_t last_pts;
-        int64_t end_pts;
-
-        /// marks if sub2video_update should force an initialization
-        unsigned int initialize;
-    } sub2video;
-} InputFilterPriv;
-
-static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
-{
-    return (InputFilterPriv*)ifilter;
-}
-
-typedef struct FPSConvContext {
-    AVFrame          *last_frame;
-    /* number of frames emitted by the video-encoding sync code */
-    int64_t           frame_number;
-    /* history of nb_frames_prev, i.e. the number of times the
-     * previous frame was duplicated by vsync code in recent
-     * do_video_out() calls */
-    int64_t           frames_prev_hist[3];
-
-    uint64_t          dup_warning;
-
-    int               last_dropped;
-    int               dropped_keyframe;
-
-    enum VideoSyncMethod vsync_method;
-
-    AVRational        framerate;
-    AVRational        framerate_max;
-    const AVRational *framerate_supported;
-    int               framerate_clip;
-} FPSConvContext;
-
-typedef struct OutputFilterPriv {
-    OutputFilter            ofilter;
-
-    int                     index;
-
-    void                   *log_parent;
-    char                    log_name[32];
-
-    char                   *name;
-
-    AVFilterContext        *filter;
-
-    /* desired output stream properties */
-    int                     format;
-    int                     width, height;
-    int                     sample_rate;
-    AVChannelLayout         ch_layout;
-    enum AVColorSpace       color_space;
-    enum AVColorRange       color_range;
-
-    AVFrameSideData       **side_data;
-    int                     nb_side_data;
-
-    // time base in which the output is sent to our downstream
-    // does not need to match the filtersink's timebase
-    AVRational              tb_out;
-    // at least one frame with the above timebase was sent
-    // to our downstream, so it cannot change anymore
-    int                     tb_out_locked;
-
-    AVRational              sample_aspect_ratio;
-
-    AVDictionary           *sws_opts;
-    AVDictionary           *swr_opts;
-
-    // those are only set if no format is specified and the encoder gives us multiple options
-    // They point directly to the relevant lists of the encoder.
-    const int              *formats;
-    const AVChannelLayout  *ch_layouts;
-    const int              *sample_rates;
-    const enum AVColorSpace *color_spaces;
-    const enum AVColorRange *color_ranges;
-
-    AVRational              enc_timebase;
-    int64_t                 trim_start_us;
-    int64_t                 trim_duration_us;
-    // offset for output timestamps, in AV_TIME_BASE_Q
-    int64_t                 ts_offset;
-    int64_t                 next_pts;
-    FPSConvContext          fps;
-
-    unsigned                flags;
-} OutputFilterPriv;
-
-static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
-{
-    return (OutputFilterPriv*)ofilter;
-}
-
 typedef struct FilterCommand {
     char *target;
     char *command;
diff --git a/fftools/ffmpeg_filter.h b/fftools/ffmpeg_filter.h
new file mode 100644
index 0000000000..94b94beece
--- /dev/null
+++ b/fftools/ffmpeg_filter.h
@@ -0,0 +1,234 @@
+/*
+ * 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_FFMPEG_FILTER_H
+#define FFTOOLS_FFMPEG_FILTER_H
+
+#include "ffmpeg.h"
+
+#include <stdint.h>
+
+#include "ffmpeg_sched.h"
+#include "sync_queue.h"
+
+#include "libavfilter/avfilter.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/dict.h"
+#include "libavutil/fifo.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/downmix_info.h"
+
+typedef struct FilterGraphPriv {
+    FilterGraph      fg;
+
+    // name used for logging
+    char             log_name[32];
+
+    int              is_simple;
+    // true when the filtergraph contains only meta filters
+    // that do not modify the frame data
+    int              is_meta;
+    // source filters are present in the graph
+    int              have_sources;
+    int              disable_conversions;
+
+    unsigned         nb_outputs_done;
+
+    const char      *graph_desc;
+
+    int              nb_threads;
+
+    // frame for temporarily holding output from the filtergraph
+    AVFrame         *frame;
+    // frame for sending output to the encoder
+    AVFrame         *frame_enc;
+
+    Scheduler       *sch;
+    unsigned         sch_idx;
+
+    AVBPrint graph_print_buf;
+
+} FilterGraphPriv;
+
+static inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
+{
+    return (FilterGraphPriv*)fg;
+}
+
+static inline const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
+{
+    return (const FilterGraphPriv*)fg;
+}
+
+typedef struct InputFilterPriv {
+    InputFilter         ifilter;
+
+    InputFilterOptions  opts;
+
+    int                 index;
+
+    AVFilterContext    *filter;
+
+    // used to hold submitted input
+    AVFrame            *frame;
+
+    /* for filters that are not yet bound to an input stream,
+     * this stores the input linklabel, if any */
+    uint8_t            *linklabel;
+
+    // filter data type
+    enum AVMediaType    type;
+    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
+    // same as type otherwise
+    enum AVMediaType    type_src;
+
+    int                 eof;
+    int                 bound;
+    int                 drop_warned;
+    uint64_t            nb_dropped;
+
+    // parameters configured for this input
+    int                 format;
+
+    int                 width, height;
+    AVRational          sample_aspect_ratio;
+    enum AVColorSpace   color_space;
+    enum AVColorRange   color_range;
+
+    int                 sample_rate;
+    AVChannelLayout     ch_layout;
+
+    AVRational          time_base;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
+    AVFifo             *frame_queue;
+
+    AVBufferRef        *hw_frames_ctx;
+
+    int                 displaymatrix_present;
+    int                 displaymatrix_applied;
+    int32_t             displaymatrix[9];
+
+    int                 downmixinfo_present;
+    AVDownmixInfo       downmixinfo;
+
+    struct {
+        AVFrame *frame;
+
+        int64_t last_pts;
+        int64_t end_pts;
+
+        /// marks if sub2video_update should force an initialization
+        unsigned int initialize;
+    } sub2video;
+} InputFilterPriv;
+
+static inline InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
+{
+    return (InputFilterPriv*)ifilter;
+}
+
+typedef struct FPSConvContext {
+    AVFrame          *last_frame;
+    /* number of frames emitted by the video-encoding sync code */
+    int64_t           frame_number;
+    /* history of nb_frames_prev, i.e. the number of times the
+     * previous frame was duplicated by vsync code in recent
+     * do_video_out() calls */
+    int64_t           frames_prev_hist[3];
+
+    uint64_t          dup_warning;
+
+    int               last_dropped;
+    int               dropped_keyframe;
+
+    enum VideoSyncMethod vsync_method;
+
+    AVRational        framerate;
+    AVRational        framerate_max;
+    const AVRational *framerate_supported;
+    int               framerate_clip;
+} FPSConvContext;
+
+
+typedef struct OutputFilterPriv {
+    OutputFilter            ofilter;
+
+    int                     index;
+
+    void                   *log_parent;
+    char                    log_name[32];
+
+    char                   *name;
+
+    AVFilterContext        *filter;
+
+    /* desired output stream properties */
+    int                     format;
+    int                     width, height;
+    int                     sample_rate;
+    AVChannelLayout         ch_layout;
+    enum AVColorSpace       color_space;
+    enum AVColorRange       color_range;
+
+    AVFrameSideData       **side_data;
+    int                     nb_side_data;
+
+    // time base in which the output is sent to our downstream
+    // does not need to match the filtersink's timebase
+    AVRational              tb_out;
+    // at least one frame with the above timebase was sent
+    // to our downstream, so it cannot change anymore
+    int                     tb_out_locked;
+
+    AVRational              sample_aspect_ratio;
+
+    AVDictionary           *sws_opts;
+    AVDictionary           *swr_opts;
+
+    // those are only set if no format is specified and the encoder gives us multiple options
+    // They point directly to the relevant lists of the encoder.
+    const int              *formats;
+    const AVChannelLayout  *ch_layouts;
+    const int              *sample_rates;
+    const enum AVColorSpace *color_spaces;
+    const enum AVColorRange *color_ranges;
+
+    AVRational              enc_timebase;
+    int64_t                 trim_start_us;
+    int64_t                 trim_duration_us;
+    // offset for output timestamps, in AV_TIME_BASE_Q
+    int64_t                 ts_offset;
+    int64_t                 next_pts;
+    FPSConvContext          fps;
+
+    unsigned                flags;
+} OutputFilterPriv;
+
+static inline OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
+{
+    return (OutputFilterPriv*)ofilter;
+}
+
+#endif /* FFTOOLS_FFMPEG_FILTER_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 10/14] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (8 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 09/14] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 11/14] fftools/resources: Add resource manager files softworkz
                           ` (4 subsequent siblings)
  14 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/APIchanges         |  3 +++
 libavfilter/avfilter.c |  9 +++++++++
 libavfilter/avfilter.h | 12 ++++++++++++
 3 files changed, 24 insertions(+)

diff --git a/doc/APIchanges b/doc/APIchanges
index 75d66f87f3..d0869561f3 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
+  Add avfilter_link_get_hw_frames_ctx().
+
 2025-04-21 - xxxxxxxxxx - lavu 60.2.100 - log.h
   Add AV_CLASS_CATEGORY_HWDEVICE.
 
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index 64c1075c40..c76d43a215 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -989,6 +989,15 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx)
     return pads[pad_idx].type;
 }
 
+AVBufferRef *avfilter_link_get_hw_frames_ctx(AVFilterLink *link)
+{
+    FilterLink *plink = ff_filter_link(link);
+    if (plink->hw_frames_ctx)
+        return av_buffer_ref(plink->hw_frames_ctx);
+
+    return NULL;
+}
+
 static int default_filter_frame(AVFilterLink *link, AVFrame *frame)
 {
     return ff_filter_frame(link->dst->outputs[0], frame);
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index a89d3cf658..f85929dc5c 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -96,6 +96,18 @@ const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
  */
 enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
 
+/**
+ * Get the hardware frames context of a filter link.
+ *
+ * @param link an AVFilterLink
+ *
+ * @return a ref-counted copy of the link's hw_frames_ctx field if there is
+ *         a hardware frames context associated with the link or NULL otherwise.
+ *         The returned AVBufferRef needs to be released with av_buffer_unref()
+ *         when it is no longer used.
+ */
+AVBufferRef* avfilter_link_get_hw_frames_ctx(AVFilterLink *link);
+
 /**
  * Lists of formats / etc. supported by an end of a link.
  *
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 11/14] fftools/resources: Add resource manager files
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (9 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 10/14] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 12/14] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
                           ` (3 subsequent siblings)
  14 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 ffbuild/common.mak           |  28 ++-
 fftools/Makefile             |   3 +-
 fftools/resources/.gitignore |   4 +
 fftools/resources/Makefile   |  27 +++
 fftools/resources/graph.css  | 353 +++++++++++++++++++++++++++++++++++
 fftools/resources/graph.html |  86 +++++++++
 fftools/resources/resman.c   | 213 +++++++++++++++++++++
 fftools/resources/resman.h   |  50 +++++
 8 files changed, 762 insertions(+), 2 deletions(-)
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h

diff --git a/ffbuild/common.mak b/ffbuild/common.mak
index ca45a0f368..6717092d44 100644
--- a/ffbuild/common.mak
+++ b/ffbuild/common.mak
@@ -139,6 +139,32 @@ else
 	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
 endif
 
+# 1) Preprocess CSS to a minified version
+%.css.min: %.css
+	# Must start with a tab in the real Makefile
+	sed 's!/\\*.*\\*/!!g' $< \
+	| tr '\n' ' ' \
+	| tr -s ' ' \
+	| sed 's/^ //; s/ $$//' \
+	> $@
+
+# 2) Gzip the minified CSS
+%.css.min.gz: %.css.min
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) >$@
+
+# 3) Convert the gzipped CSS to a .c array
+%.css.c: %.css.min.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
+# 4) Gzip the HTML file (no minification needed)
+%.html.gz: TAG = GZIP
+%.html.gz: %.html
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) > $@
+
+# 5) Convert the gzipped HTML to a .c array
+%.html.c: %.html.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
 clean::
 	$(RM) $(BIN2CEXE) $(CLEANSUFFIXES:%=ffbuild/%)
 
@@ -214,7 +240,7 @@ $(TOOLOBJS): | tools
 
 OUTDIRS := $(OUTDIRS) $(dir $(OBJS) $(HOBJS) $(HOSTOBJS) $(SLIBOBJS) $(SHLIBOBJS) $(STLIBOBJS) $(TESTOBJS))
 
-CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
+CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *.html.gz *.html.c *.css.gz *.css.c  *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
 LIBSUFFIXES       = *.a *.lib *.so *.so.* *.dylib *.dll *.def *.dll.a
 
 define RULES
diff --git a/fftools/Makefile b/fftools/Makefile
index e9c9891c34..a30bec889e 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -42,7 +42,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -56,6 +56,7 @@ all: $(AVPROGS)
 fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
+OUTDIRS += fftools/resources
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/resources/.gitignore b/fftools/resources/.gitignore
new file mode 100644
index 0000000000..5f496535a6
--- /dev/null
+++ b/fftools/resources/.gitignore
@@ -0,0 +1,4 @@
+*.html.c
+*.css.c
+*.html.gz
+*.css.gz
diff --git a/fftools/resources/Makefile b/fftools/resources/Makefile
new file mode 100644
index 0000000000..f3a0d0a970
--- /dev/null
+++ b/fftools/resources/Makefile
@@ -0,0 +1,27 @@
+clean::
+	$(RM) $(CLEANSUFFIXES:%=fftools/resources/%)
+
+
+HTML_RESOURCES := fftools/resources/graph.html \
+
+# .html => (gzip) .html.gz => (bin2c) .html.c => (cc) .o
+HTML_RESOURCES_GZ := $(HTML_RESOURCES:.html=.html.gz)
+HTML_RESOURCES_C := $(HTML_RESOURCES_GZ:.html.gz=.html.c)
+HTML_RESOURCES_OBJS := $(HTML_RESOURCES_C:.c=.o)
+
+CSS_RESOURCES := fftools/resources/graph.css   \
+
+# .css => (sh) .css.min  => (gzip) .css.min.gz => (bin2c) .css.c => (cc) .o
+CSS_RESOURCES_MIN := $(CSS_RESOURCES:.css=.css.min)
+CSS_RESOURCES_GZ := $(CSS_RESOURCES_MIN:.css.min=.css.min.gz)
+CSS_RESOURCES_C := $(CSS_RESOURCES_GZ:.css.min.gz=.css.c)
+CSS_RESOURCES_OBJS := $(CSS_RESOURCES_C:.c=.o)
+
+# Uncomment to prevent deletion
+#.PRECIOUS: %.css.c %.css.min %.css.gz  %.css.min.gz
+
+OBJS-resman +=                  \
+    fftools/resources/resman.o          \
+    $(HTML_RESOURCES_OBJS)      \
+    $(CSS_RESOURCES_OBJS)       \
+
diff --git a/fftools/resources/graph.css b/fftools/resources/graph.css
new file mode 100644
index 0000000000..ab480673ab
--- /dev/null
+++ b/fftools/resources/graph.css
@@ -0,0 +1,353 @@
+/* Variables */
+.root {
+    --ff-colvideo: #6eaa7b;
+    --ff-colaudio: #477fb3;
+    --ff-colsubtitle: #ad76ab;
+    --ff-coltext: #666;
+}
+
+/* Common & Misc */
+.ff-inputfiles rect, .ff-outputfiles rect, .ff-inputstreams rect, .ff-outputstreams rect, .ff-decoders rect, .ff-encoders rect {
+    stroke-width: 0;
+    stroke: transparent;
+    filter: none !important;
+    fill: transparent !important;
+    display: none !important;
+}
+
+.cluster span {
+    color: var(--ff-coltext);
+}
+
+.cluster rect {
+    stroke: #dfdfdf !important;
+    transform: translateY(-2.3rem);
+    filter: drop-shadow(1px 2px 2px rgba(185,185,185,0.2)) !important;
+    rx: 8;
+    ry: 8;
+}
+
+.cluster-label {
+    font-size: 1.1rem;
+}
+
+    .cluster-label .nodeLabel {
+        display: block;
+        font-weight: 500;
+        color: var(--ff-coltext);
+    }
+
+    .cluster-label div {
+        max-width: unset !important;
+        padding: 3px;
+    }
+
+    .cluster-label foreignObject {
+        transform: translateY(-0.7rem);
+    }
+
+/* Input and output files */
+.node.ff-inputfile .label foreignObject, .node.ff-outputfile .label foreignObject {
+    overflow: visible;
+}
+
+.cluster.ff-inputfile .cluster-label foreignObject div:not(foreignObject div div), .cluster.ff-outputfile .cluster-label foreignObject div:not(foreignObject div div) {
+    display: table !important;
+}
+
+.nodeLabel div.ff-inputfile, .nodeLabel div.ff-outputfile {
+    font-size: 1.1rem;
+    font-weight: 500;
+    min-width: 14rem;
+    width: 100%;
+    display: flex;
+    color: var(--ff-coltext);
+    margin-top: 0.1rem;
+    line-height: 1.35;
+    padding-bottom: 1.9rem;
+}
+
+.nodeLabel div.ff-outputfile {
+    flex-direction: row-reverse;
+}
+
+.ff-inputfile .index, .ff-outputfile .index {
+    order: 2;
+    color: var(--ff-coltext);
+    text-align: center;
+    border-radius: 0.45rem;
+    border: 0.18em solid #666666db;
+    font-weight: 600;
+    padding: 0 0.3em;
+    opacity: 0.8;
+}
+
+    .ff-inputfile .index::before {
+        content: 'In ';
+    }
+
+    .ff-outputfile .index::before {
+        content: 'Out ';
+    }
+
+.ff-inputfile .demuxer_name, .ff-outputfile .muxer_name {
+    flex: 1;
+    order: 1;
+    font-size: 0.9rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: center;
+    max-width: 8rem;
+    align-content: center;
+    margin: 0.2rem 0.4rem 0 0.4rem;
+}
+
+.ff-inputfile .file_extension, .ff-outputfile .file_extension {
+    order: 0;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.45rem;
+    font-weight: 600;
+    padding: 0 0.4em;
+    align-content: center;
+    opacity: 0.8;
+}
+
+.ff-inputfile .url, .ff-outputfile .url {
+    order: 4;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0.75rem;
+    font-size: 0.7rem;
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin: 0 0.3rem;
+    direction: rtl;
+    color: #999;
+}
+
+.cluster.ff-inputfile rect, .cluster.ff-outputfile rect {
+    transform: translateY(-1.8rem);
+    fill: url(#ff-radgradient);
+}
+
+/* Input and output streams */
+.node.ff-inputstream rect, .node.ff-outputstream rect {
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    fill: white;
+    stroke: #e5e5e5 !important;
+    height: 2.7rem;
+    transform: translateY(0.2rem);
+    filter: none;
+    rx: 3;
+    ry: 3;
+}
+
+.node.ff-inputstream .label foreignObject, .node.ff-outputstream .label foreignObject {
+    transform: translateY(-0.2%);
+    overflow: visible;
+}
+
+    .node.ff-inputstream .label foreignObject div:not(foreignObject div div), .node.ff-outputstream .label foreignObject div:not(foreignObject div div) {
+        display: block !important;
+        line-height: 1.5 !important;
+    }
+
+.nodeLabel div.ff-inputstream, .nodeLabel div.ff-outputstream {
+    font-size: 1.0rem;
+    font-weight: 500;
+    min-width: 12rem;
+    width: 100%;
+    display: flex;
+}
+
+.nodeLabel div.ff-outputstream {
+    flex-direction: row-reverse;
+}
+
+.ff-inputstream .name, .ff-outputstream .name {
+    flex: 1;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: left;
+    align-content: center;
+    margin-bottom: -0.15rem;
+}
+
+.ff-inputstream .index, .ff-outputstream .index {
+    flex: 0 0 1.4rem;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.3rem;
+    font-weight: 600;
+    margin-right: -0.3rem;
+    margin-left: 0.4rem;
+    opacity: 0.8;
+}
+
+.ff-outputstream .index {
+    margin-right: 0.6rem;
+    margin-left: -0.4rem;
+}
+
+.ff-inputstream::before, .ff-outputstream::before {
+    font-variant-emoji: text;
+    flex: 0 0 2rem;
+    margin-left: -0.8rem;
+    margin-right: 0.2rem;
+}
+
+.ff-outputstream::before {
+    margin-left: 0.2rem;
+    margin-right: -0.6rem;
+}
+
+.ff-inputstream.video::before, .ff-outputstream.video::before {
+    content: '\239A';
+    color: var(--ff-colvideo);
+    font-size: 2.25rem;
+    line-height: 0.5;
+    font-weight: bold;
+}
+
+.ff-inputstream.audio::before, .ff-outputstream.audio::before {
+    content: '\1F39D';
+    color: var(--ff-colaudio);
+    font-size: 1.75rem;
+    line-height: 0.9;
+}
+
+.ff-inputstream.subtitle::before, .ff-outputstream.subtitle::before {
+    content: '\1AC';
+    color: var(--ff-colsubtitle);
+    font-size: 1.2rem;
+    line-height: 1.1;
+    transform: scaleX(1.5);
+    margin-top: 0.050rem;
+}
+
+.ff-inputstream.attachment::before, .ff-outputstream.attachment::before {
+    content: '\1F4CE';
+    font-size: 1.3rem;
+    line-height: 1.15;
+}
+
+.ff-inputstream.data::before, .ff-outputstream.data::before {
+    content: '\27E8\2219\2219\2219\27E9';
+    font-size: 1.15rem;
+    line-height: 1.17;
+    letter-spacing: -0.3px;
+}
+
+/* Filter Graphs */
+.cluster.ff-filters rect {
+    stroke-dasharray: 6 !important;
+    stroke-width: 1.3px;
+    stroke: #d1d1d1 !important;
+    filter: none !important;
+}
+
+.cluster.ff-filters div.ff-filters .id {
+    display: none;
+}
+
+.cluster.ff-filters div.ff-filters .name {
+    margin-right: 0.5rem;
+    font-size: 0.9rem;
+}
+
+.cluster.ff-filters div.ff-filters .description {
+    font-weight: 400;
+    font-size: 0.75rem;
+    vertical-align: middle;
+    color: #777;
+    font-family: Cascadia Code, Lucida Console, monospace;
+}
+
+/* Filter Shapes */
+.node.ff-filter rect {
+    rx: 10;
+    ry: 10;
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.node.ff-filter .label foreignObject {
+    transform: translateY(-0.4rem);
+    overflow: visible;
+}
+
+.nodeLabel div.ff-filter {
+    font-size: 1.0rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    min-width: 5.5rem;
+    margin-bottom: 0.5rem;
+}
+
+    .nodeLabel div.ff-filter span {
+        color: inherit;
+    }
+
+/* Decoders & Encoders */
+.node.ff-decoder rect, .node.ff-encoder rect {
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.nodeLabel div.ff-decoder, .nodeLabel div.ff-encoder {
+    font-size: 0.85rem;
+    font-weight: 500;
+    min-width: 3.5rem;
+}
+
+/* Links and Arrows */
+path.flowchart-link[id|='video'] {
+    stroke: var(--ff-colvideo);
+}
+
+path.flowchart-link[id|='audio'] {
+    stroke: var(--ff-colaudio);
+}
+
+path.flowchart-link[id|='subtitle'] {
+    stroke: var(--ff-colsubtitle);
+}
+
+marker.marker path {
+    fill: context-stroke;
+}
+
+.edgeLabel foreignObject {
+    transform: translateY(-1rem);
+}
+
+.edgeLabel p {
+    background: transparent;
+    white-space: nowrap;
+    margin: 1rem 0.5rem !important;
+    font-weight: 500;
+    color: var(--ff-coltext);
+}
+
+.edgeLabel, .labelBkg {
+    background: transparent;
+}
+
+.edgeLabels .edgeLabel * {
+    font-size: 0.8rem;
+}
diff --git a/fftools/resources/graph.html b/fftools/resources/graph.html
new file mode 100644
index 0000000000..cd94276fd4
--- /dev/null
+++ b/fftools/resources/graph.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <title>FFmpeg Graph</title>
+</head>
+<body>
+<style>
+    html {
+        color: #666;
+        font-family: Roboto;
+        height: 100%;
+    }
+
+    body {
+        background-color: #f9f9f9;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        margin: 0;
+        padding: 1.7rem 1.7rem 3.5rem 1.7rem;
+    }
+
+    div#banner {
+        align-items: center;
+        display: flex;
+        flex-direction: row;
+        margin-bottom: 1.5rem;
+        margin-left: 0.6vw;
+    }
+
+    div#header {
+        aspect-ratio: 1/1;
+        background-image: url(https://trac.ffmpeg.org/ffmpeg-logo.png);
+        background-size: cover;
+        width: 1.6rem;
+    }
+
+    h1 {
+        font-size: 1.2rem;
+        margin: 0 0.5rem;
+    }
+
+    pre.mermaid {
+        align-items: center;
+        background-color: white;
+        box-shadow: 2px 2px 25px 0px #00000010;
+        color: transparent;
+        display: flex;
+        flex: 1;
+        justify-content: center;
+        margin: 0;
+        overflow: overlay;
+    }
+
+    pre.mermaid svg {
+        height: auto;
+        margin: 0;
+        max-width: unset !important;
+        width: auto;
+    }
+
+    pre.mermaid svg * {
+        user-select: none;
+    }
+</style>
+<div id="banner">
+    <div id="header"></div>
+    <h1>FFmpeg Execution Graph</h1>
+</div>
+<pre class="mermaid">
+__###__
+</pre>
+<script type="module">
+        import vanillaJsWheelZoom from 'https://cdn.jsdelivr.net/npm/vanilla-js-wheel-zoom@9.0.4/+esm';
+        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
+        function initViewer() {
+            var element = document.querySelector('.mermaid svg')
+            vanillaJsWheelZoom.create('pre.mermaid svg', { type: 'html', smoothTimeDrag: 0, width: element.clientWidth, height: element.clientHeight, maxScale: 3 });
+        }
+        mermaid.initialize({ startOnLoad: false }); 
+        document.fonts.ready.then(() => { mermaid.run({ querySelector: '.mermaid', postRenderCallback: initViewer }); });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/fftools/resources/resman.c b/fftools/resources/resman.c
new file mode 100644
index 0000000000..488aaeecf6
--- /dev/null
+++ b/fftools/resources/resman.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <zlib.h>
+#include "resman.h"
+#include <libavformat/url.h>
+#include "fftools/ffmpeg_filter.h"
+#include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+
+extern const unsigned char ff_graph_html_data[];
+extern const unsigned int ff_graph_html_len;
+
+extern const unsigned char ff_graph_css_data[];
+extern const unsigned ff_graph_css_len;
+
+static const FFResourceDefinition resource_definitions[] = {
+    [FF_RESOURCE_GRAPH_CSS]   = { FF_RESOURCE_GRAPH_CSS,   "graph.css",   &ff_graph_css_data[0],   &ff_graph_css_len   },
+    [FF_RESOURCE_GRAPH_HTML]  = { FF_RESOURCE_GRAPH_HTML,  "graph.html",  &ff_graph_html_data[0],  &ff_graph_html_len  },
+};
+
+
+static const AVClass resman_class = {
+    .class_name = "ResourceManager",
+};
+
+typedef struct ResourceManagerContext {
+    const AVClass *class;
+    AVDictionary *resource_dic;
+} ResourceManagerContext;
+
+static AVMutex mutex = AV_MUTEX_INITIALIZER;
+
+ResourceManagerContext *resman_ctx = NULL;
+
+
+static int decompress_gzip(ResourceManagerContext *ctx, uint8_t *in, unsigned in_len, char **out, size_t *out_len)
+{
+    z_stream strm;
+    unsigned chunk = 65534;
+    int ret;
+    uint8_t *buf;
+
+    *out = NULL;
+    memset(&strm, 0, sizeof(strm));
+
+    // Allocate output buffer with extra byte for null termination
+    buf = (uint8_t *)av_mallocz(chunk + 1);
+    if (!buf) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to allocate decompression buffer\n");
+        return AVERROR(ENOMEM);
+    }
+
+    // 15 + 16 tells zlib to detect GZIP or zlib automatically
+    ret = inflateInit2(&strm, 15 + 16);
+    if (ret != Z_OK) {
+        av_log(ctx, AV_LOG_ERROR, "Error during zlib initialization: %s\n", strm.msg);
+        av_free(buf);
+        return AVERROR(ENOSYS);
+    }
+
+    strm.avail_in  = in_len;
+    strm.next_in   = in;
+    strm.avail_out = chunk;
+    strm.next_out  = buf;
+
+    ret = inflate(&strm, Z_FINISH);
+    if (ret != Z_OK && ret != Z_STREAM_END) {
+        av_log(ctx, AV_LOG_ERROR, "Inflate failed: %d, %s\n", ret, strm.msg);
+        inflateEnd(&strm);
+        av_free(buf);
+        return (ret == Z_STREAM_END) ? Z_OK : ((ret == Z_OK) ? Z_BUF_ERROR : ret);
+    }
+
+    if (strm.avail_out == 0) {
+        // TODO: Error or loop decoding?
+        av_log(ctx, AV_LOG_WARNING, "Decompression buffer may be too small\n");
+    }
+
+    *out_len = chunk - strm.avail_out;
+    buf[*out_len] = 0; // Ensure null termination
+
+    inflateEnd(&strm);
+    *out = (char *)buf;
+    return Z_OK;
+}
+
+static ResourceManagerContext *get_resman_context(void)
+{
+    ResourceManagerContext *res = resman_ctx;
+
+    ff_mutex_lock(&mutex);
+
+    if (res)
+        goto end;
+
+    res = av_mallocz(sizeof(ResourceManagerContext));
+    if (!res) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to allocate resource manager context\n");
+        goto end;
+    }
+
+    res->class = &resman_class;
+    resman_ctx = res;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
+
+
+void ff_resman_uninit(void)
+{
+    ff_mutex_lock(&mutex);
+
+    if (resman_ctx) {
+        if (resman_ctx->resource_dic)
+            av_dict_free(&resman_ctx->resource_dic);
+        av_freep(&resman_ctx);
+    }
+
+    ff_mutex_unlock(&mutex);
+}
+
+
+char *ff_resman_get_string(FFResourceId resource_id)
+{
+    ResourceManagerContext *ctx               = get_resman_context();
+    FFResourceDefinition resource_definition = { 0 };
+    AVDictionaryEntry *dic_entry;
+    char *res = NULL;
+
+    if (!ctx)
+        return NULL;
+
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(resource_definitions); ++i) {
+        FFResourceDefinition def = resource_definitions[i];
+        if (def.resource_id == resource_id) {
+            resource_definition = def;
+            break;
+        }
+    }
+
+    if (!resource_definition.name) {
+        av_log(ctx, AV_LOG_ERROR, "Unable to find resource with ID %d\n", resource_id);
+        return NULL;
+    }
+
+    ff_mutex_lock(&mutex);
+
+    dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+    if (!dic_entry) {
+        char *out = NULL;
+        size_t out_len;
+        int dict_ret;
+
+        int ret = decompress_gzip(ctx, (uint8_t *)resource_definition.data, *resource_definition.data_len, &out, &out_len);
+
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to decompress the resource with ID %d\n", resource_id);
+            goto end;
+        }
+
+        dict_ret = av_dict_set(&ctx->resource_dic, resource_definition.name, out, 0);
+        if (dict_ret < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to store decompressed resource in dictionary: %d\n", dict_ret);
+            av_freep(&out);
+            goto end;
+        }
+
+        av_freep(&out);
+        dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+        if (!dic_entry) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to retrieve resource from dictionary after storing it\n");
+            goto end;
+        }
+    }
+
+    res = dic_entry->value;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
diff --git a/fftools/resources/resman.h b/fftools/resources/resman.h
new file mode 100644
index 0000000000..6485db5091
--- /dev/null
+++ b/fftools/resources/resman.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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_RESOURCES_RESMAN_H
+#define FFTOOLS_RESOURCES_RESMAN_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "fftools/ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+#include "fftools/textformat/avtextformat.h"
+
+typedef enum {
+    FF_RESOURCE_GRAPH_CSS,
+    FF_RESOURCE_GRAPH_HTML,
+} FFResourceId;
+
+typedef struct FFResourceDefinition {
+    FFResourceId resource_id;
+    const char *name;
+
+    const unsigned char *data;
+    const unsigned *data_len;
+
+} FFResourceDefinition;
+
+void ff_resman_uninit(void);
+
+char *ff_resman_get_string(FFResourceId resource_id);
+
+#endif /* FFTOOLS_RESOURCES_RESMAN_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 12/14] fftools/ffmpeg_mux: Make ms_from_ost() inline
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (10 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 11/14] fftools/resources: Add resource manager files softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 13/14] fftools/graphprint: Add execution graph printing softworkz
                           ` (2 subsequent siblings)
  14 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_mux.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fftools/ffmpeg_mux.h b/fftools/ffmpeg_mux.h
index f41f2c18fa..4ca8ab73a4 100644
--- a/fftools/ffmpeg_mux.h
+++ b/fftools/ffmpeg_mux.h
@@ -123,7 +123,7 @@ typedef struct Muxer {
 
 int mux_check_init(void *arg);
 
-static MuxStream *ms_from_ost(OutputStream *ost)
+static inline MuxStream *ms_from_ost(OutputStream *ost)
 {
     return (MuxStream*)ost;
 }
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 13/14] fftools/graphprint: Add execution graph printing
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (11 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 12/14] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 14/14] fftools/graphprint: Now, make it a Killer-Feature! softworkz
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
  14 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

The key benefits are:

- Different to other graph printing methods, this is outputting:
  - all graphs with runtime state
    (including auto-inserted filters)
  - each graph with its inputs and outputs
  - all filters with their in- and output pads
  - all connections between all input- and output pads
  - for each connection:
    - the runtime-negotiated format and media type
    - the hw context
    - if video hw context, both: hw pixfmt + sw pixfmt
- Output can either be printed to stdout or written to specified file
- Output is machine-readable
- Use the same output implementation as ffprobe, supporting multiple
  formats

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi                   |   10 +
 fftools/Makefile                  |   20 +-
 fftools/ffmpeg.c                  |    4 +
 fftools/ffmpeg.h                  |    3 +
 fftools/ffmpeg_filter.c           |    5 +
 fftools/ffmpeg_opt.c              |   13 +
 fftools/graph/graphprint.c        | 1103 +++++++++++++++++++++++++++++
 fftools/graph/graphprint.h        |   30 +
 fftools/textformat/avtextformat.c |    2 +
 fftools/textformat/avtextformat.h |   29 +
 fftools/textformat/tf_mermaid.c   |  658 +++++++++++++++++
 fftools/textformat/tf_mermaid.h   |   41 ++
 12 files changed, 1917 insertions(+), 1 deletion(-)
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 17ba876ea3..35675b5309 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1394,6 +1394,16 @@ It is on by default, to explicitly disable it you need to specify @code{-nostats
 @item -stats_period @var{time} (@emph{global})
 Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
 
+@item -print_graphs (@emph{global})
+Prints execution graph details to stderr in the format set via -print_graphs_format.
+
+@item -print_graphs_file @var{filename} (@emph{global})
+Writes execution graph details to the specified file in the format set via -print_graphs_format.
+
+@item -print_graphs_format @var{format} (@emph{global})
+Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
+The default format is json.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index a30bec889e..361a4fd574 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -9,6 +9,8 @@ AVBASENAMES  = ffmpeg ffplay ffprobe
 ALLAVPROGS   = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
 ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
 
+include $(SRC_PATH)/fftools/resources/Makefile
+
 OBJS-ffmpeg +=                  \
     fftools/ffmpeg_dec.o        \
     fftools/ffmpeg_demux.o      \
@@ -19,8 +21,21 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
+    fftools/graph/graphprint.o        \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
+    fftools/textformat/avtextformat.o \
+    fftools/textformat/tf_compact.o   \
+    fftools/textformat/tf_default.o   \
+    fftools/textformat/tf_flat.o      \
+    fftools/textformat/tf_ini.o       \
+    fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
+    fftools/textformat/tf_xml.o       \
+    fftools/textformat/tw_avio.o      \
+    fftools/textformat/tw_buffer.o    \
+    fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffprobe +=                       \
     fftools/textformat/avtextformat.o \
@@ -29,10 +44,12 @@ OBJS-ffprobe +=                       \
     fftools/textformat/tf_flat.o      \
     fftools/textformat/tf_ini.o       \
     fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
     fftools/textformat/tf_xml.o       \
     fftools/textformat/tw_avio.o      \
     fftools/textformat/tw_buffer.o    \
     fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffplay += fftools/ffplay_renderer.o
 
@@ -42,7 +59,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources fftools/graph
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -57,6 +74,7 @@ fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
 OUTDIRS += fftools/resources
+OUTDIRS += fftools/graph
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..6766ec209c 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -81,6 +81,7 @@
 #include "ffmpeg.h"
 #include "ffmpeg_sched.h"
 #include "ffmpeg_utils.h"
+#include "graph/graphprint.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
+    if (print_graphs || print_graphs_file)
+        print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
+
     if (do_benchmark) {
         int64_t maxrss = getmaxrss() / 1024;
         av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 5869979214..7fbf0ad532 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -717,6 +717,9 @@ extern float max_error_rate;
 extern char *filter_nbthreads;
 extern int filter_complex_nbthreads;
 extern int vstats_version;
+extern int print_graphs;
+extern char *print_graphs_file;
+extern char *print_graphs_format;
 extern int auto_conversion_filters;
 
 extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index eab9487f97..b774606562 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -22,6 +22,7 @@
 
 #include "ffmpeg.h"
 #include "ffmpeg_filter.h"
+#include "graph/graphprint.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -2983,6 +2984,10 @@ read_frames:
     }
 
 finish:
+
+    if (print_graphs || print_graphs_file)
+        print_filtergraph(fg, fgt.graph);
+
     // EOF is normal termination
     if (ret == AVERROR_EOF)
         ret = 0;
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 6ec325f51e..3d1efe32f9 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -47,6 +47,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/stereo3d.h"
+#include "graph/graphprint.h"
 
 HWDevice *filter_hw_device;
 
@@ -75,6 +76,9 @@ float max_error_rate  = 2.0/3;
 char *filter_nbthreads;
 int filter_complex_nbthreads = 0;
 int vstats_version = 2;
+int print_graphs = 0;
+char *print_graphs_file = NULL;
+char *print_graphs_format = NULL;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1735,6 +1739,15 @@ const OptionDef options[] = {
         { .func_arg = opt_filter_complex_script },
         "deprecated, use -/filter_complex instead", "filename" },
 #endif
+    { "print_graphs",   OPT_TYPE_BOOL, 0,
+        { &print_graphs },
+        "print execution graph data to stderr" },
+    { "print_graphs_file", OPT_TYPE_STRING, 0,
+        { &print_graphs_file },
+        "write execution graph data to the specified file", "filename" },
+    { "print_graphs_format", OPT_TYPE_STRING, 0,
+        { &print_graphs_format },
+      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
new file mode 100644
index 0000000000..01e19178b7
--- /dev/null
+++ b/fftools/graph/graphprint.c
@@ -0,0 +1,1103 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include <string.h>
+#include <stdatomic.h>
+
+#include "graphprint.h"
+
+#include <libavformat/url.h>
+
+#include "fftools/ffmpeg_filter.h"
+#include "fftools/ffmpeg_mux.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+#include "fftools/textformat/avtextformat.h"
+#include "fftools/textformat/tf_mermaid.h"
+#include "fftools/resources/resman.h"
+
+typedef enum {
+    SECTION_ID_ROOT,
+    SECTION_ID_FILTERGRAPHS,
+    SECTION_ID_FILTERGRAPH,
+    SECTION_ID_GRAPH_INPUTS,
+    SECTION_ID_GRAPH_INPUT,
+    SECTION_ID_GRAPH_OUTPUTS,
+    SECTION_ID_GRAPH_OUTPUT,
+    SECTION_ID_FILTERS,
+    SECTION_ID_FILTER,
+    SECTION_ID_FILTER_INPUTS,
+    SECTION_ID_FILTER_INPUT,
+    SECTION_ID_FILTER_OUTPUTS,
+    SECTION_ID_FILTER_OUTPUT,
+    SECTION_ID_HWFRAMESCONTEXT,
+    SECTION_ID_INPUTFILES,
+    SECTION_ID_INPUTFILE,
+    SECTION_ID_INPUTSTREAMS,
+    SECTION_ID_INPUTSTREAM,
+    SECTION_ID_OUTPUTFILES,
+    SECTION_ID_OUTPUTFILE,
+    SECTION_ID_OUTPUTSTREAMS,
+    SECTION_ID_OUTPUTSTREAM,
+    SECTION_ID_STREAMLINKS,
+    SECTION_ID_STREAMLINK,
+    SECTION_ID_DECODERS,
+    SECTION_ID_DECODER,
+    SECTION_ID_ENCODERS,
+    SECTION_ID_ENCODER,
+} SectionID;
+
+static struct AVTextFormatSection sections[] = {
+    [SECTION_ID_ROOT]            = { SECTION_ID_ROOT, "root", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER, { SECTION_ID_FILTERGRAPHS, SECTION_ID_INPUTFILES, SECTION_ID_OUTPUTFILES, SECTION_ID_DECODERS, SECTION_ID_ENCODERS, SECTION_ID_STREAMLINKS, -1 } },
+
+    [SECTION_ID_FILTERGRAPHS]    = { SECTION_ID_FILTERGRAPHS, "graphs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
+    [SECTION_ID_FILTERGRAPH]     = { SECTION_ID_FILTERGRAPH, "graph", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { SECTION_ID_GRAPH_INPUTS, SECTION_ID_GRAPH_OUTPUTS, SECTION_ID_FILTERS, -1 }, .element_name = "graph_info" },
+
+    [SECTION_ID_GRAPH_INPUTS]    = { SECTION_ID_GRAPH_INPUTS, "graph_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_INPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_INPUT]     = { SECTION_ID_GRAPH_INPUT, "graph_input", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_GRAPH_OUTPUTS]   = { SECTION_ID_GRAPH_OUTPUTS, "graph_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_OUTPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_OUTPUT]    = { SECTION_ID_GRAPH_OUTPUT, "graph_output", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTERS]         = { SECTION_ID_FILTERS, "filters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_FILTER, -1 }, .id_key = "graph_id" },
+    [SECTION_ID_FILTER]          = { SECTION_ID_FILTER, "filter", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { SECTION_ID_FILTER_INPUTS, SECTION_ID_FILTER_OUTPUTS, -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_INPUTS]   = { SECTION_ID_FILTER_INPUTS, "filter_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_INPUT, -1 } },
+    [SECTION_ID_FILTER_INPUT]    = { SECTION_ID_FILTER_INPUT, "filter_input", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "source_filter_id", .dest_id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_OUTPUTS]  = { SECTION_ID_FILTER_OUTPUTS, "filter_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_OUTPUT, -1 } },
+    [SECTION_ID_FILTER_OUTPUT]   = { SECTION_ID_FILTER_OUTPUT, "filter_output", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "filter_id", .dest_id_key = "dest_filter_id" },
+
+    [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context",  0, { -1 }, },
+
+    [SECTION_ID_INPUTFILES]      = { SECTION_ID_INPUTFILES, "inputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTFILE]       = { SECTION_ID_INPUTFILE, "inputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_INPUTSTREAMS]    = { SECTION_ID_INPUTSTREAMS, "inputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTSTREAM]     = { SECTION_ID_INPUTSTREAM, "inputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTFILES]     = { SECTION_ID_OUTPUTFILES, "outputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTFILE]      = { SECTION_ID_OUTPUTFILE, "outputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTSTREAMS]   = { SECTION_ID_OUTPUTSTREAMS, "outputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTSTREAM]    = { SECTION_ID_OUTPUTSTREAM, "outputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id", },
+
+    [SECTION_ID_STREAMLINKS]     = { SECTION_ID_STREAMLINKS, "streamlinks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAMLINK, -1 } },
+    [SECTION_ID_STREAMLINK]      = { SECTION_ID_STREAMLINK, "streamlink", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .src_id_key = "source_stream_id", .dest_id_key = "dest_stream_id" },
+
+    [SECTION_ID_DECODERS]        = { SECTION_ID_DECODERS, "decoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_DECODER, -1 } },
+    [SECTION_ID_DECODER]         = { SECTION_ID_DECODER, "decoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "source_id", .dest_id_key = "id" },
+
+    [SECTION_ID_ENCODERS]        = { SECTION_ID_ENCODERS, "encoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_ENCODER, -1 } },
+    [SECTION_ID_ENCODER]         = { SECTION_ID_ENCODER, "encoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "id", .dest_id_key = "dest_id" },
+};
+
+typedef struct GraphPrintContext {
+    AVTextFormatContext *tfc;
+    AVTextWriterContext *wctx;
+    AVDiagramConfig diagram_config;
+
+    int id_prefix_num;
+    int is_diagram;
+    int opt_flags;
+    int skip_buffer_filters;
+    AVBPrint pbuf;
+
+} GraphPrintContext;
+
+/* Text Format API Shortcuts */
+#define print_id(k, v)          print_sanizied_id(gpc, k, v, 0)
+#define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
+#define print_int(k, v)         avtext_print_integer(tfc, k, v)
+#define print_int_opt(k, v)     avtext_print_integer_flags(tfc, k, v, gpc->opt_flags)
+#define print_q(k, v, s)        avtext_print_rational(tfc, k, v, s)
+#define print_str(k, v)         avtext_print_string(tfc, k, v, 0)
+#define print_str_opt(k, v)     avtext_print_string(tfc, k, v, gpc->opt_flags)
+#define print_val(k, v, u)      avtext_print_unit_int(tfc, k, v, u)
+
+#define print_fmt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, 0);    \
+} while (0)
+
+#define print_fmt_opt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, gpc->opt_flags);    \
+} while (0)
+
+
+static atomic_int prefix_num = 0;
+
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    unsigned i;
+    for (i = 0; src[i] && i < dst_size - 1; i++)
+        dst[i]      = (char)av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static char *get_extension(const char *url)
+{
+    const char *ext;
+    URLComponents uc;
+    int ret;
+    char scratchpad[128];
+
+    if (!url)
+        return 0;
+
+    ret = ff_url_decompose(&uc, url, NULL);
+    if (ret < 0)
+        return NULL;
+    for (ext = uc.query; *ext != '.' && ext > uc.path; ext--) {
+    }
+
+    if (*ext != '.')
+        return 0;
+    if (uc.query - ext > sizeof(scratchpad))
+        return NULL; //not enough memory in our scratchpad
+    av_strlcpy(scratchpad, ext + 1, uc.query - ext);
+
+    return av_strdup(scratchpad);
+}
+
+static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+
+    if (!hw_device_context)
+        return;
+
+    print_int_opt("has_hw_device_context", 1);
+    print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
+}
+
+static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    const AVPixFmtDescriptor *pix_desc_hw;
+    const AVPixFmtDescriptor *pix_desc_sw;
+
+    if (!hw_frames_context || !hw_frames_context->device_ctx)
+        return;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT);
+
+    print_int_opt("has_hw_frames_context", 1);
+    print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
+
+    pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
+    if (pix_desc_hw) {
+        print_str("hw_pixel_format", pix_desc_hw->name);
+        if (pix_desc_hw->alias)
+            print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
+    }
+
+    pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
+    if (pix_desc_sw) {
+        print_str("sw_pixel_format", pix_desc_sw->name);
+        if (pix_desc_sw->alias)
+            print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
+    }
+
+    print_int_opt("width", hw_frames_context->width);
+    print_int_opt("height", hw_frames_context->height);
+    print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBufferRef *hw_frames_ctx;
+    char layout_string[64];
+
+    if (!link)
+        return;
+
+    hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
+
+    print_str_opt("media_type", av_get_media_type_string(link->type));
+
+    switch (link->type) {
+    case AVMEDIA_TYPE_VIDEO:
+
+        if (hw_frames_ctx && hw_frames_ctx->data) {
+            AVHWFramesContext *      hwfctx      = (AVHWFramesContext *)hw_frames_ctx->data;
+            const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
+            const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
+            if (pix_desc_hw && pix_desc_sw)
+                print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
+        } else {
+            print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
+        }
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        print_q("sar", link->sample_aspect_ratio, ':');
+
+        if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
+            print_str_opt("color_range", av_color_range_name(link->color_range));
+
+        if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
+            print_str("color_space", av_color_space_name(link->colorspace));
+        break;
+
+    case AVMEDIA_TYPE_SUBTITLE:
+        ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        break;
+
+    case AVMEDIA_TYPE_AUDIO:
+        av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
+        print_str("channel_layout", layout_string);
+        print_val("channels", link->ch_layout.nb_channels, "ch");
+        if (tfc->show_value_unit)
+            print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
+        else
+            print_val("sample_rate", link->sample_rate, "Hz");
+
+        break;
+    }
+
+    print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
+
+    if (hw_frames_ctx && hw_frames_ctx->data)
+        print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
+}
+
+static char sanitize_char(const char c)
+{
+    if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+        return c;
+    return '_';
+}
+
+static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBPrint buf;
+
+    if (!key || !id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    print_str(key, buf.str);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+    AVBPrint buf;
+
+    if (!id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    sec_ctx.context_id = buf.str;
+
+    avtext_print_section_header(tfc, &sec_ctx, section_id);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static const char *get_filterpad_name(const AVFilterPad *pad)
+{
+    return pad ? avfilter_pad_get_name(pad, 0) : "pad";
+}
+
+static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
+
+    ////print_id("filter_id", filter->name);
+
+    if (filter->filter) {
+        print_str("filter_name", filter->filter->name);
+        print_str_opt("description", filter->filter->description);
+        print_int_opt("nb_inputs", filter->nb_inputs);
+        print_int_opt("nb_outputs", filter->nb_outputs);
+    }
+
+    if (filter->hw_device_ctx) {
+        AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
+        print_hwdevicecontext(gpc, device_context);
+        if (filter->extra_hw_frames > 0)
+            print_int("extra_hw_frames", filter->extra_hw_frames);
+    }
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
+
+    for (unsigned i = 0; i < filter->nb_inputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->inputs[i];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
+        sec_ctx.context_type = NULL;
+
+        print_int_opt("input_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
+
+        dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
+        if (dic_entry) {
+            char buf[256];
+            (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
+            print_id_noprefix("source_filter_id", buf);
+        } else {
+            print_id("source_filter_id", link->src->name);
+        }
+
+        print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
+
+    for (unsigned i = 0; i < filter->nb_outputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->outputs[i];
+        char buf[256];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
+        sec_ctx.context_type = NULL;
+
+        dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
+        if (dic_entry) {
+            (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
+            print_id_noprefix("dest_filter_id", buf);
+        } else {
+            print_id("dest_filter_id", link->dst->name);
+        }
+
+        print_int_opt("output_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->srcpad));
+        ////print_id("dest_filter_id", link->dst->name);
+        print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER
+}
+
+static void init_sections(void)
+{
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
+        sections[i].show_all_entries = 1;
+}
+
+static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVDictionary *input_map = NULL;
+    AVDictionary *output_map = NULL;
+
+    print_int("graph_index", fg->index);
+    print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+    print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
+    print_str("description", fgp->graph_desc);
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
+
+    for (int i = 0; i < fg->nb_inputs; i++) {
+        InputFilterPriv *ifilter = ifp_from_ifilter(fg->inputs[i]);
+        enum AVMediaType media_type = ifilter->type;
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
+
+        print_int("input_index", ifilter->index);
+
+        if (ifilter->linklabel)
+            print_str("link_label", (const char*)ifilter->linklabel);
+
+        if (ifilter->filter) {
+            print_id("filter_id", ifilter->filter->name);
+            print_str("filter_name", ifilter->filter->filter->name);
+        }
+
+        if (ifilter->linklabel && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
+        else if (ifilter->opts.name && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->opts.name, 0);
+
+        print_str("media_type", av_get_media_type_string(media_type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
+
+    for (int i = 0; i < fg->nb_outputs; i++) {
+        OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
+
+        print_int("output_index", ofilter->index);
+
+        print_str("name", ofilter->name);
+
+        if (fg->outputs[i]->linklabel)
+            print_str("link_label", (const char*)fg->outputs[i]->linklabel);
+
+        if (ofilter->filter) {
+            print_id("filter_id", ofilter->filter->name);
+            print_str("filter_name", ofilter->filter->filter->name);
+        }
+
+        if (ofilter->name && ofilter->filter)
+            av_dict_set(&output_map, ofilter->filter->name, ofilter->name, 0);
+
+
+        print_str("media_type", av_get_media_type_string(fg->outputs[i]->type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
+
+    if (graph) {
+        AVTextFormatSectionContext sec_ctx = { 0 };
+
+        sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTERS);
+
+        if (gpc->is_diagram) {
+            print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+            print_str("description", fgp->graph_desc);
+            print_str("id", sec_ctx.context_id);
+        }
+
+        av_freep(&sec_ctx.context_id);
+
+        for (unsigned i = 0; i < graph->nb_filters; i++) {
+            AVFilterContext *filter = graph->filters[i];
+
+            if (gpc->skip_buffer_filters) {
+                if (av_dict_get(input_map, filter->name, NULL, 0))
+                    continue;
+                if (av_dict_get(output_map, filter->name, NULL, 0))
+                    continue;
+            }
+
+            sec_ctx.context_id = filter->name;
+
+            print_filter(gpc, filter, input_map, output_map);
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
+    }
+
+    // Clean up dictionaries
+    av_dict_free(&input_map);
+    av_dict_free(&output_map);
+}
+
+static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    AVTextFormatContext       *tfc = gpc->tfc;
+    AVBPrint                   buf;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    sec_ctx.context_id = "Inputs";
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+    print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
+
+    for (int n = nb_ifiles - 1; n >= 0; n--) {
+        InputFile *ifi = ifiles[n];
+        AVFormatContext *fc = ifi->ctx;
+
+        sec_ctx.context_id = av_asprintf("Input_%d", n);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
+        av_freep(&sec_ctx.context_id);
+
+        print_fmt("index", "%d", ifi->index);
+
+        if (fc) {
+            print_str("demuxer_name", fc->iformat->name);
+            if (fc->url) {
+                char *extension = get_extension(fc->url);
+                if (extension) {
+                    print_str("file_extension", extension);
+                    av_freep(&extension);
+                }
+                print_str("url", fc->url);
+            }
+        }
+
+        sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
+
+        av_freep(&sec_ctx.context_id);
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+            const AVCodecDescriptor *codec_desc;
+
+            if (!ist || !ist->par)
+                continue;
+
+            codec_desc = avcodec_descriptor_get(ist->par->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
+
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_in_%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else if (ist->dec) {
+                char char_buf[256];
+                av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
+                av_bprintf(&buf, "%s", "Attachment");
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
+                av_bprintf(&buf, "%s", "Data");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ist->index);
+
+            if (ist->dec)
+                print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
+
+
+    print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
+
+    for (int n = 0; n < nb_ifiles; n++) {
+        InputFile *ifi = ifiles[n];
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+
+            if (!ist->decoder)
+                continue;
+
+            sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_DECODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("source_id", "r_in_%d_%d", n, i);
+            print_fmt("id", "in_%d_%d", n, i);
+
+            ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            print_fmt("name", "%s", ist->dec->name);
+
+            print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_DECODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            ////const AVCodecDescriptor *codec_desc;
+
+            if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
+                continue;
+
+            ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_ENCODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "out__%d_%d", n, i);
+            print_fmt("dest_id", "r_out__%d_%d", n, i);
+
+            print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
+
+    for (int n = nb_ofiles - 1; n >= 0; n--) {
+        OutputFile *of = ofiles[n];
+        Muxer *muxer = (Muxer *)of;
+
+        if (!muxer->fc)
+            continue;
+
+        sec_ctx.context_id = av_asprintf("Output_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
+
+        av_freep(&sec_ctx.context_id);
+
+        ////print_str_opt("index", av_get_media_type_string(of->index));
+        print_fmt("index", "%d", of->index);
+        ////print_str("url", of->url);
+        print_str("muxer_name", muxer->fc->oformat->name);
+        if (of->url) {
+            char *extension = get_extension(of->url);
+            if (extension) {
+                print_str("file_extension", extension);
+                av_freep(&extension);
+            }
+            print_str("url", of->url);
+        }
+
+        sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_out__%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else {
+                av_bprintf(&buf, "%s", "unknown");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ost->index);
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
+
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->ist && !ost->filter) {
+                sec_ctx.context_type = av_get_media_type_string(ost->type);
+                avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_STREAMLINK);
+                sec_ctx.context_type = NULL;
+
+                if (ost->enc) {
+                    print_fmt("dest_stream_id", "out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Transcode");
+                } else {
+                    print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Stream Copy");
+                }
+
+                print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+                avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
+
+    return 0;
+}
+
+
+static void uninit_graphprint(GraphPrintContext *gpc)
+{
+    if (gpc->tfc)
+        avtext_context_close(&gpc->tfc);
+
+    if (gpc->wctx)
+        avtextwriter_context_close(&gpc->wctx);
+
+    // Finalize the print buffer if it was initialized
+    av_bprint_finalize(&gpc->pbuf, NULL);
+}
+
+static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
+{
+    const AVTextFormatter *text_formatter;
+    AVTextFormatContext *tfc = NULL;
+    AVTextWriterContext *wctx = NULL;
+    GraphPrintContext *gpc = NULL;
+    char *w_args = NULL;
+    char *w_name;
+    int ret;
+
+    init_sections();
+    *pgpc = NULL;
+
+    av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("json");
+    if (!print_graphs_format) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &w_args);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    text_formatter = avtext_get_formatter_by_name(w_name);
+    if (!text_formatter) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtextwriter_create_buffer(&wctx, target_buf);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    AVTextFormatOptions tf_options = { .show_optional_fields = -1 };
+    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), tf_options, NULL);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    gpc = av_mallocz(sizeof(GraphPrintContext));
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    gpc->wctx = wctx;
+    gpc->tfc = tfc;
+    av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
+    gpc->is_diagram = !!(tfc->formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
+    if (gpc->is_diagram) {
+        tfc->show_value_unit = 1;
+        tfc->show_optional_fields = -1;
+        gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+        gpc->skip_buffer_filters = 1;
+        ////} else {
+        ////    gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+    }
+
+    if (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml")) {
+        gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
+
+        if (!strcmp(text_formatter->name, "mermaidhtml"))
+            gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
+
+        av_diagram_init(tfc, &gpc->diagram_config);
+    }
+
+    *pgpc = gpc;
+
+    return 0;
+
+fail:
+    if (tfc)
+        avtext_context_close(&tfc);
+    if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
+        avtextwriter_context_close(&wctx);
+    av_freep(&gpc);
+
+    return ret;
+}
+
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVBPrint *target_buf = &fgp->graph_print_buf;
+    int ret;
+
+    if (!fg || !fgp) {
+        av_log(NULL, AV_LOG_ERROR, "Invalid filter graph provided\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (target_buf->len)
+        av_bprint_finalize(target_buf, NULL);
+
+    ret = init_graphprint(&gpc, target_buf);
+    if (ret)
+        return ret;
+
+    if (!gpc) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to initialize graph print context\n");
+        return AVERROR(ENOMEM);
+    }
+
+    tfc = gpc->tfc;
+
+    // Due to the threading model each graph needs to print itself into a buffer
+    // from its own thread. The actual printing happens short before cleanup in ffmpeg.c
+    // where all graphs are assembled together. To make this work, we need to put the
+    // formatting context into the same state like it would be when printing all at once,
+    // so here we print the section headers and clear the buffer to get into the right state.
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+
+    av_bprint_clear(target_buf);
+
+    print_filtergraph_single(gpc, fg, graph);
+
+    if (gpc->is_diagram) {
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+    }
+
+    uninit_graphprint(gpc);
+
+    return 0;
+}
+
+static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    AVBPrint target_buf;
+    int ret;
+
+    ret = init_graphprint(&gpc, &target_buf);
+    if (ret)
+        goto cleanup;
+
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto cleanup;
+    }
+
+    tfc = gpc->tfc;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+
+    for (int i = 0; i < nb_graphs; i++) {
+        FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
+        AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+        if (graph_buf->len > 0) {
+            avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+            av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+            av_bprint_finalize(graph_buf, NULL);
+            avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        }
+    }
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->fg_simple) {
+                FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
+                AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+                if (graph_buf->len > 0) {
+                    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+                    av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+                    av_bprint_finalize(graph_buf, NULL);
+                    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+                }
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+
+    print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ROOT
+
+    if (print_graphs_file) {
+        AVIOContext *avio = NULL;
+
+        if (!strcmp(print_graphs_file, "-")) {
+            printf("%s", target_buf.str);
+        } else {
+            ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
+                goto cleanup;
+            }
+
+            avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
+            avio_flush(avio);
+
+            if ((ret = avio_closep(&avio)) < 0)
+                av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
+        }
+    }
+
+    if (print_graphs)
+        av_log(NULL, AV_LOG_INFO, "%s    %c", target_buf.str, '\n');
+
+cleanup:
+    // Properly clean up resources
+    if (gpc)
+        uninit_graphprint(gpc);
+
+    // Ensure the target buffer is properly finalized
+    av_bprint_finalize(&target_buf, NULL);
+
+    return ret;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+}
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
new file mode 100644
index 0000000000..9f043cc273
--- /dev/null
+++ b/fftools/graph/graphprint.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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_GRAPH_GRAPHPRINT_H
+#define FFTOOLS_GRAPH_GRAPHPRINT_H
+
+#include "fftools/ffmpeg.h"
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles);
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 4dae024814..c19338c09c 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -698,6 +698,8 @@ static void formatters_register_all(void)
     registered_formatters[4] = &avtextformatter_ini;
     registered_formatters[5] = &avtextformatter_json;
     registered_formatters[6] = &avtextformatter_xml;
+    registered_formatters[7] = &avtextformatter_mermaid;
+    registered_formatters[8] = &avtextformatter_mermaidhtml;
 }
 
 const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c97a477fa9..97ec8fd62d 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -31,6 +31,12 @@
 
 #define SECTION_MAX_NB_CHILDREN 11
 
+typedef struct AVTextFormatSectionContext {
+    char *context_id;
+    const char *context_type;
+    int context_flags;
+} AVTextFormatSectionContext;
+
 
 typedef struct AVTextFormatSection {
     int id;             ///< unique id identifying a section
@@ -42,6 +48,10 @@ typedef struct AVTextFormatSection {
                                            ///  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
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE       32 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS      64 ///< ...
+#define AV_TEXTFORMAT_SECTION_PRINT_TAGS         128 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH   256 ///< ...
 
     int flags;
     const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
@@ -50,12 +60,17 @@ typedef struct AVTextFormatSection {
     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;
+    const char *id_key;          ///< name of the key to be used as the id 
+    const char *src_id_key;     ///< name of the key to be used as the source id for diagram connections
+    const char *dest_id_key;   ///< name of the key to be used as the target id for diagram connections
+    const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
 } AVTextFormatSection;
 
 typedef struct AVTextFormatContext AVTextFormatContext;
 
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
+#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER         4
 
 typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
@@ -64,6 +79,18 @@ typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_NB
 } StringValidation;
 
+typedef enum {
+    AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_NONDIR,
+    AV_TEXTFORMAT_LINKTYPE_HIDDEN,
+    AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
+} AVTextFormatLinkType;
+
 typedef struct AVTextFormatter {
     const AVClass *priv_class;      ///< private class of the formatter, if any
     int priv_size;                  ///< private size for the formatter context
@@ -175,5 +202,7 @@ extern const AVTextFormatter avtextformatter_flat;
 extern const AVTextFormatter avtextformatter_ini;
 extern const AVTextFormatter avtextformatter_json;
 extern const AVTextFormatter avtextformatter_xml;
+extern const AVTextFormatter avtextformatter_mermaid;
+extern const AVTextFormatter avtextformatter_mermaidhtml;
 
 #endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
diff --git a/fftools/textformat/tf_mermaid.c b/fftools/textformat/tf_mermaid.c
new file mode 100644
index 0000000000..6147cf6eea
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.c
@@ -0,0 +1,658 @@
+/*
+ * 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 "tf_internal.h"
+#include "tf_mermaid.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+
+static const char *init_directive = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 10,"
+        "\"nodeSpacing\": 10,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"flowchart\": { "
+            "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
+            "\"diagramPadding\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char* init_directive_er = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"layout\": \"elk\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 65,"
+        "\"nodeSpacing\": 60,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"er\": { "
+            "\"diagramPadding\": 12, "
+            "\"entityPadding\": 4, "
+            "\"minEntityWidth\": 150, "
+            "\"minEntityHeight\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char *theme_css_er = ""
+
+    // Variables
+            ".root { "
+                "--ff-colvideo: #6eaa7b; "
+                "--ff-colaudio: #477fb3; "
+                "--ff-colsubtitle: #ad76ab; "
+                "--ff-coltext: #666; "
+            "} "
+            " g.nodes g.node.default rect.basic.label-container, "
+            " g.nodes g.node.default path { "
+            "     rx: 1; "
+            "     ry: 1; "
+            "     stroke-width: 1px !important; "
+            "     stroke: #e9e9e9 !important; "
+            "     fill: url(#ff-filtergradient) !important; "
+            "     filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
+            "     fill: white !important; "
+            " } "
+            "  "
+            " .relationshipLine { "
+            "     stroke: gray; "
+            "     stroke-width: 1; "
+            "     fill: none; "
+            "     filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
+            " } "
+            "  "
+            " g.node.default g.label.name  foreignObject > div > span > p, "
+            " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
+            "     font-size: 0.95rem; "
+            "     font-weight: 500; "
+            "     text-transform: uppercase; "
+            "     min-width: 5.5rem; "
+            "     margin-bottom: 0.5rem; "
+            "      "
+            " } "
+            "  "
+            " .edgePaths path { "
+            "     marker-end: none; "
+            "     marker-start: none; "
+            "  "
+            "} ";
+
+
+/* Mermaid Graph output */
+
+typedef struct MermaidContext {
+    const AVClass *class;
+    AVDiagramConfig *diagram_config;
+    int subgraph_count;
+    int within_tag;
+    int indent_level;
+    int create_html;
+
+    // Options
+    int enable_link_colors; // Requires Mermaid 11.5
+
+    struct section_data {
+        const char *section_id;
+        const char *section_type;
+        const char *src_id;
+        const char *dest_id;
+        AVTextFormatLinkType link_type;
+        int current_is_textblock;
+        int current_is_stadium;
+        int subgraph_start_incomplete;
+    }  section_data[SECTION_MAX_NB_LEVELS];
+
+    unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint link_buf; ///< print buffer for writing diagram links
+    AVDictionary *link_dict;
+} MermaidContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(MermaidContext, x)
+
+static const AVOption mermaid_options[] = {
+    { "link_coloring",    "enable colored links (requires Mermaid >= 11.5)",  OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL,   { .i64 = 1 },  0, 1 },
+    ////{"diagram_css",      "CSS for the diagram",                              OFFSET(diagram_css),        AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    ////{"html_template",    "Template HTML",                                    OFFSET(html_template),      AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    { NULL },
+};
+
+DEFINE_FORMATTER_CLASS(mermaid);
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
+{
+    MermaidContext *mmc = tfc->priv;
+    mmc->diagram_config = diagram_config;
+}
+
+static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
+{
+    MermaidContext *mmc = tfc->priv;
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&buf, "%s--%s", src, dest);
+
+    if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
+        return 1;
+
+    av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
+
+    return 0;
+}
+
+static av_cold int mermaid_init(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
+    return 0;
+}
+
+static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    int ret = mermaid_init(tfc);
+
+    if (ret < 0)
+        return ret;
+
+    mmc->create_html = 1;
+
+    return 0;
+}
+
+#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
+
+static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
+{
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSectionContext *sec_ctx = data;
+
+    if (tfc->level == 0) {
+        char *directive;
+        AVBPrint css_buf;
+        const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
+        char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
+        (void)theme_css_er;
+        ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
+        av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+        av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
+        av_freep(&single_line_css);
+
+        directive = av_strireplace(diag_directive, "__###__", css_buf.str);
+        if (mmc->create_html) {
+            uint64_t length;
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+
+            length = token_pos - mmc->diagram_config->html_template;
+            for (uint64_t i = 0; i < length; i++)
+                writer_w8(tfc, mmc->diagram_config->html_template[i]);
+        }
+
+        writer_put_str(tfc, directive);
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+            writer_put_str(tfc, "flowchart LR\n");
+        ////writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
+            writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%);     \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%);   \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%);     \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            writer_put_str(tfc, "erDiagram\n");
+            break;
+        }
+
+        return;
+    }
+
+    if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
+        AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
+
+        if (parent_sec_data.subgraph_start_incomplete) {
+
+            if (parent_buf->len > 0)
+                writer_printf(tfc, "%s", parent_buf->str);
+
+            writer_put_str(tfc, "</div>\"]\n");
+
+            mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
+        }
+    }
+
+    av_freep(&mmc->section_data[tfc->level].section_id);
+    av_freep(&mmc->section_data[tfc->level].section_type);
+    av_freep(&mmc->section_data[tfc->level].src_id);
+    av_freep(&mmc->section_data[tfc->level].dest_id);
+    mmc->section_data[tfc->level].current_is_textblock = 0;
+    mmc->section_data[tfc->level].current_is_stadium = 0;
+    mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
+    mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
+
+    // NOTE: av_strdup() allocations aren't checked
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+            MM_INDENT();
+            writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+
+            mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+                if (sec_ctx->context_flags & 1) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_textblock = 1;
+                } else if (sec_ctx->context_flags & 2) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s([\"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_stadium = 1;
+                } else {
+                    MM_INDENT();
+                    writer_printf(tfc, "%s(\"", sec_ctx->context_id);
+                }
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+                MM_INDENT();
+                writer_printf(tfc, "%s {\n", sec_ctx->context_id);
+                break;
+            }
+
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
+
+        if (sec_ctx && sec_ctx->context_type)
+            writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
+        else
+            writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        av_bprint_clear(buf);
+        mmc->nb_link_captions[tfc->level] = 0;
+
+        if (sec_ctx && sec_ctx->context_type)
+            mmc->section_data[tfc->level].section_type = av_strdup(sec_ctx->context_type);
+
+        ////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(tfc, " type=\"%s\"", buf.str);
+    }
+}
+
+static void mermaid_print_section_footer(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
+        writer_put_str(tfc, "</div>");
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (sec_data.current_is_textblock) {
+                writer_printf(tfc, "\"}\n", section->name);
+
+                if (sec_data.section_id) {
+                    MM_INDENT();
+                    writer_put_str(tfc, "class ");
+                    writer_put_str(tfc, sec_data.section_id);
+                    writer_put_str(tfc, " ff-");
+                    writer_put_str(tfc, section->name);
+                    writer_put_str(tfc, "\n");
+                }
+            } else if (sec_data.current_is_stadium) {
+                writer_printf(tfc, "\"]):::ff-%s\n", section->name);
+            } else {
+                writer_printf(tfc, "\"):::ff-%s\n", section->name);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            MM_INDENT();
+            writer_put_str(tfc, "}\n\n");
+            break;
+        }
+
+        mmc->indent_level--;
+
+    } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
+
+        MM_INDENT();
+        writer_put_str(tfc, "end\n");
+
+        if (sec_data.section_id) {
+            MM_INDENT();
+            writer_put_str(tfc, "class ");
+            writer_put_str(tfc, sec_data.section_id);
+            writer_put_str(tfc, " ff-");
+            writer_put_str(tfc, section->name);
+            writer_put_str(tfc, "\n");
+        }
+
+        mmc->indent_level--;
+    }
+
+    if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
+        if (sec_data.src_id && sec_data.dest_id
+            && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+
+                if (sec_data.section_type && mmc->enable_link_colors)
+                    av_bprintf(&mmc->link_buf, "\n  %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
+                else
+                    av_bprintf(&mmc->link_buf, "\n  %s ==", sec_data.src_id);
+
+                if (buf->len > 0) {
+                    av_bprintf(&mmc->link_buf, " \"%s", buf->str);
+
+                    for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
+                        av_bprintf(&mmc->link_buf, "<br>&nbsp;");
+
+                    av_bprintf(&mmc->link_buf, "\" ==");
+                }
+
+                av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+
+                av_bprintf(&mmc->link_buf, "\n  %s", sec_data.src_id);
+
+                switch (sec_data.link_type) {
+                case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
+                    break;
+                default:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                }
+
+                av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
+
+                break;
+            }
+
+    if (tfc->level == 0) {
+
+        writer_put_str(tfc, "\n");
+        if (mmc->create_html) {
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+            token_pos += strlen("__###__");
+            writer_put_str(tfc, token_pos);
+        }
+    }
+
+    if (tfc->level == 1) {
+
+        if (mmc->link_buf.len > 0) {
+            writer_put_str(tfc, mmc->link_buf.str);
+            av_bprint_clear(&mmc->link_buf);
+        }
+
+        writer_put_str(tfc, "\n");
+    }
+}
+
+static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
+                                const char *str, int64_t num, const int is_int)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+    int exit = 0;
+
+    if (section->id_key && !strcmp(section->id_key, key)) {
+        mmc->section_data[tfc->level].section_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
+        mmc->section_data[tfc->level].dest_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->src_id_key && !strcmp(section->src_id_key, key)) {
+        mmc->section_data[tfc->level].src_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->linktype_key && !strcmp(section->linktype_key, key)) {
+        mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
+        exit = 1;
+    }
+
+    //if (exit)
+    //    return;
+
+    if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
+        || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
+
+        if (exit)
+            return;
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (is_int) {
+                writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
+            } else {
+                ////AVBPrint b;
+                ////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
+                const char *tmp = av_strireplace(str, "\"", "'");
+                ////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
+                writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
+                av_freep(&tmp);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+            if (!is_int && str)
+            {
+                const char *col_type;
+
+                if (key[0] == '_')
+                    return;
+
+                if (sec_data.section_id && !strcmp(str, sec_data.section_id))
+                    col_type = "PK";
+                else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
+                    col_type = "FK";
+                else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
+                    col_type = "FK";
+                else
+                    col_type = "";
+
+                MM_INDENT();
+
+                if (is_int)
+                    writer_printf(tfc, "    %s %"PRId64" %s\n", key, num, col_type);
+                else
+                    writer_printf(tfc, "    %s %s %s\n", key, str, col_type);
+            }
+            break;
+        }
+
+    } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        if (exit)
+            return;
+
+        if (buf->len > 0)
+            av_bprintf(buf, "%s", "<br>");
+
+        av_bprintf(buf, "");
+        if (is_int)
+            av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
+        else
+            av_bprintf(buf, "<span>%s</span>", str);
+
+        mmc->nb_link_captions[tfc->level]++;
+    }
+}
+
+static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
+{
+    mermaid_print_value(tfc, key, value, 0, 0);
+}
+
+static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
+{
+    mermaid_print_value(tfc, key, NULL, value, 1);
+}
+
+const AVTextFormatter avtextformatter_mermaid = {
+    .name                 = "mermaid",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
+
+
+const AVTextFormatter avtextformatter_mermaidhtml = {
+    .name                 = "mermaidhtml",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init_html,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
diff --git a/fftools/textformat/tf_mermaid.h b/fftools/textformat/tf_mermaid.h
new file mode 100644
index 0000000000..aff73bf9f3
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.h
@@ -0,0 +1,41 @@
+/*
+ * 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_TF_MERMAID_H
+#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
+
+typedef enum {
+    AV_DIAGRAMTYPE_GRAPH,
+    AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
+} AVDiagramType;
+
+typedef struct AVDiagramConfig {
+    AVDiagramType diagram_type;
+    const char *diagram_css;
+    const char *html_template;
+} AVDiagramConfig;
+
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
+
+void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
+
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */
\ No newline at end of file
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v5 14/14] fftools/graphprint: Now, make it a Killer-Feature!
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (12 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 13/14] fftools/graphprint: Add execution graph printing softworkz
@ 2025-04-22 21:55         ` softworkz
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
  14 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-22 21:55 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

remember this: -sg   <= show-graph

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi              |   4 +
 fftools/Makefile             |   1 +
 fftools/ffmpeg.c             |   2 +-
 fftools/ffmpeg.h             |   1 +
 fftools/ffmpeg_filter.c      |   2 +-
 fftools/ffmpeg_opt.c         |   4 +
 fftools/graph/filelauncher.c | 205 +++++++++++++++++++++++++++++++++++
 fftools/graph/graphprint.c   |  50 ++++++++-
 fftools/graph/graphprint.h   |  32 ++++++
 9 files changed, 296 insertions(+), 5 deletions(-)
 create mode 100644 fftools/graph/filelauncher.c

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 35675b5309..6e9e7aed0e 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1404,6 +1404,10 @@ Writes execution graph details to the specified file in the format set via -prin
 Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
 The default format is json.
 
+@item -sg (@emph{global})
+Writes the execution graph to a temporary html file (mermaidhtml format) and 
+tries to launch it in the default browser.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index 361a4fd574..56a2910212 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -22,6 +22,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
     fftools/graph/graphprint.o        \
+    fftools/graph/filelauncher.o      \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
     fftools/textformat/avtextformat.o \
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index 6766ec209c..9875a1f7fd 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -309,7 +309,7 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
 
     if (do_benchmark) {
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7fbf0ad532..49fea0307d 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -721,6 +721,7 @@ extern int print_graphs;
 extern char *print_graphs_file;
 extern char *print_graphs_format;
 extern int auto_conversion_filters;
+extern int show_graph;
 
 extern const AVIOInterruptCB int_cb;
 
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index b774606562..e82e333b7f 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -2985,7 +2985,7 @@ read_frames:
 
 finish:
 
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraph(fg, fgt.graph);
 
     // EOF is normal termination
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3d1efe32f9..24713d640f 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -79,6 +79,7 @@ int vstats_version = 2;
 int print_graphs = 0;
 char *print_graphs_file = NULL;
 char *print_graphs_format = NULL;
+int show_graph = 0;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1748,6 +1749,9 @@ const OptionDef options[] = {
     { "print_graphs_format", OPT_TYPE_STRING, 0,
         { &print_graphs_format },
       "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
+    { "sg",   OPT_TYPE_BOOL, 0,
+        { &show_graph },
+        "create execution graph as temporary html file and try to launch it in the default browser" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/filelauncher.c b/fftools/graph/filelauncher.c
new file mode 100644
index 0000000000..0cf5f15cf1
--- /dev/null
+++ b/fftools/graph/filelauncher.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(_WIN32)
+#  include <windows.h>
+#  include <shellapi.h>
+#else
+#  include <sys/time.h>
+#  include <time.h>
+#endif
+#include "graphprint.h"
+
+int ff_open_html_in_browser(const char *html_path)
+{
+    if (!html_path || !*html_path)
+        return -1;
+
+#if defined(_WIN32)
+
+    // --- Windows ---------------------------------
+    {
+        HINSTANCE rc = ShellExecuteA(NULL, "open", html_path, NULL, NULL, SW_SHOWNORMAL);
+        if ((UINT_PTR)rc <= 32) {
+            // Fallback: system("start ...")
+            char cmd[1024];
+            _snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "start \"\" \"%s\"", html_path);
+            if (system(cmd) != 0)
+                return -1;
+        }
+        return 0;
+    }
+
+#elif defined(__APPLE__)
+
+    // --- macOS -----------------------------------
+    {
+        // "open" is the macOS command to open a file/URL with the default application
+        char cmd[1024];
+        snprintf(cmd, sizeof(cmd), "open '%s' 1>/dev/null 2>&1 &", html_path);
+        if (system(cmd) != 0)
+            return -1;
+        return 0;
+    }
+
+#else
+
+    // --- Linux / Unix-like -----------------------
+    // We'll try xdg-open, then gnome-open, then kfmclient
+    {
+        // Helper macro to try one browser command
+        // Returns 0 on success, -1 on failure
+        #define TRY_CMD(prog) do {                                   \
+            char buf[1024];                                          \
+            snprintf(buf, sizeof(buf), "%s '%s' 1>/dev/null 2>&1 &", \
+                     (prog), html_path);                              \
+            int ret = system(buf);                                    \
+            /* On Unix: system() returns -1 if the shell can't run. */\
+            /* Otherwise, check exit code in lower 8 bits.           */\
+            if (ret != -1 && WIFEXITED(ret) && WEXITSTATUS(ret) == 0) \
+                return 0;                                             \
+        } while (0)
+
+        TRY_CMD("xdg-open");
+        TRY_CMD("gnome-open");
+        TRY_CMD("kfmclient exec");
+
+        fprintf(stderr, "Could not open '%s' in a browser.\n", html_path);
+        return -1;
+    }
+
+#endif
+}
+
+
+int ff_get_temp_dir(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    // --- Windows ------------------------------------
+    {
+        // GetTempPathA returns length of the string (including trailing backslash).
+        // If the return value is greater than buffer size, it's an error.
+        DWORD len = GetTempPathA((DWORD)size, buf);
+        if (len == 0 || len > size) {
+            // Could not retrieve or buffer is too small
+            return -1;
+        }
+        return 0;
+    }
+
+#else
+
+    // --- macOS / Linux / Unix -----------------------
+    // Follow typical POSIX convention: check common env variables
+    // and fallback to /tmp if not found.
+    {
+        const char *tmp = getenv("TMPDIR");
+        if (!tmp || !*tmp) tmp = getenv("TMP");
+        if (!tmp || !*tmp) tmp = getenv("TEMP");
+        if (!tmp || !*tmp) tmp = "/tmp";
+
+        // Copy into buf, ensure there's a trailing slash
+        size_t len = strlen(tmp);
+        if (len + 2 > size) {
+            // Need up to len + 1 for slash + 1 for null terminator
+            return -1;
+        }
+
+        strcpy(buf, tmp);
+        // Append slash if necessary
+        if (buf[len - 1] != '/' && buf[len - 1] != '\\') {
+#if defined(__APPLE__)
+            // On macOS/Unix, use forward slash
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#else
+            // Technically on Unix it's always '/', but here's how you'd do if needed:
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#endif
+        }
+        return 0;
+    }
+
+#endif
+}
+
+int ff_make_timestamped_html_name(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    /*----------- Windows version -----------*/
+    SYSTEMTIME st;
+    GetLocalTime(&st);
+    /*
+      st.wYear, st.wMonth, st.wDay,
+      st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
+    */
+    int written = _snprintf_s(buf, size, _TRUNCATE,
+                              "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                              st.wYear,
+                              st.wMonth,
+                              st.wDay,
+                              st.wHour,
+                              st.wMinute,
+                              st.wSecond,
+                              st.wMilliseconds);
+    if (written < 0)
+        return -1; /* Could not write into buffer */
+    return 0;
+
+#else
+
+    /*----------- macOS / Linux / Unix version -----------*/
+    struct timeval tv;
+    if (gettimeofday(&tv, NULL) != 0) {
+        return -1; /* gettimeofday failed */
+    }
+
+    struct tm local_tm;
+    localtime_r(&tv.tv_sec, &local_tm);
+
+    int ms = (int)(tv.tv_usec / 1000); /* convert microseconds to milliseconds */
+
+    /* 
+       local_tm.tm_year is years since 1900,
+       local_tm.tm_mon  is 0-based (0=Jan, 11=Dec) 
+    */
+    int written = snprintf(buf, size,
+                           "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                           local_tm.tm_year + 1900,
+                           local_tm.tm_mon + 1,
+                           local_tm.tm_mday,
+                           local_tm.tm_hour,
+                           local_tm.tm_min,
+                           local_tm.tm_sec,
+                           ms);
+    if (written < 0 || (size_t)written >= size) {
+        return -1; /* Buffer too small or formatting error */
+    }
+    return 0;
+
+#endif
+}
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
index 01e19178b7..635bae6d5e 100644
--- a/fftools/graph/graphprint.c
+++ b/fftools/graph/graphprint.c
@@ -586,8 +586,6 @@ static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifil
     AVBPrint                   buf;
     AVTextFormatSectionContext sec_ctx = { 0 };
 
-    sec_ctx.context_id = "Inputs";
-
     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
 
     print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
@@ -875,6 +873,11 @@ static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
 
     av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
 
+    if (show_graph) {
+        if (!print_graphs_format || strcmp(print_graphs_format, "mermaidhtml") != 0)
+            print_graphs_format = av_strdup("mermaidhtml");
+    }
+
     if (!print_graphs_format)
         print_graphs_format = av_strdup("json");
     if (!print_graphs_format) {
@@ -1099,5 +1102,46 @@ cleanup:
 
 int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
 {
-    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+    int ret;
+
+    if (show_graph) {
+        char buf[2048];
+        AVBPrint bp;
+
+        av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+        print_graphs = 0;
+
+        ret = ff_get_temp_dir(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error getting temp directory path for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        ret = ff_make_timestamped_html_name(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error creating temp file name for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        av_bprint_finalize(&bp, &print_graphs_file);
+    }
+
+    ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    if (!ret && show_graph) {
+        av_log(NULL, AV_LOG_INFO, "Execution graph saved as: %s\n", print_graphs_file);
+        av_log(NULL, AV_LOG_INFO, "Trying to launch graph in browser...\n");
+
+        ret = ff_open_html_in_browser(print_graphs_file);
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Browser could not be launched for execution graph display\nPlease open manually: %s\n", print_graphs_file);
+        }
+    }
+
+    return ret;
 }
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
index 9f043cc273..43f769870b 100644
--- a/fftools/graph/graphprint.h
+++ b/fftools/graph/graphprint.h
@@ -27,4 +27,36 @@ int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles,
 
 int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
 
+/**
+ * Open an HTML file in the default browser (Windows, macOS, Linux/Unix).
+ *
+ * @param html_path Absolute or relative path to the HTML file.
+ * @return 0 on success, -1 on failure.
+ *
+ * NOTE: This uses system() calls for non-Windows, and ShellExecute on Windows.
+ *       Exercise caution if 'html_path' is untrusted (possible command injection).
+ */
+int ff_open_html_in_browser(const char *html_path);
+
+/**
+ * Retrieve the system's temporary directory.
+ *
+ * @param buf  Output buffer to store the temp directory path (including trailing slash)
+ * @param size Size of the output buffer in bytes
+ * @return 0 on success, -1 on failure (buffer too small or other errors)
+ *
+ * Note: On most platforms, the path will include a trailing slash (e.g. "C:\\Users\\...\\Temp\\" on Windows, "/tmp/" on Unix).
+ */
+int ff_get_temp_dir(char *buf, size_t size);
+
+/**
+ * Create a timestamped HTML filename, e.g.:
+ *   ffmpeg_graph_2024-01-01_22-12-59_123.html
+ *
+ * @param buf  Pointer to buffer where the result is stored
+ * @param size Size of the buffer in bytes
+ * @return 0 on success, -1 on error (e.g. buffer too small)
+ */
+int ff_make_timestamped_html_name(char *buf, size_t size);
+
 #endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 01/14] fftools/textformat: Formatting and whitespace changes
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 01/14] fftools/textformat: Formatting and whitespace changes softworkz
@ 2025-04-23 22:08           ` Stefano Sabatini
  0 siblings, 0 replies; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-23 22:08 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Tuesday 2025-04-22 21:55:30 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/avtextformat.c  | 86 ++++++++++++++--------------
>  fftools/textformat/avtextformat.h  | 16 +++---
>  fftools/textformat/avtextwriters.h | 11 ++--
>  fftools/textformat/tf_compact.c    | 91 +++++++++++++++++-------------
>  fftools/textformat/tf_default.c    | 20 +++----
>  fftools/textformat/tf_flat.c       | 26 +++++----
>  fftools/textformat/tf_ini.c        | 36 ++++++------
>  fftools/textformat/tf_json.c       | 10 ++--
>  fftools/textformat/tf_xml.c        | 30 +++++-----
>  9 files changed, 172 insertions(+), 154 deletions(-)

Looks good to me.
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements softworkz
@ 2025-04-23 22:34           ` Stefano Sabatini
  2025-04-23 22:53             ` softworkz .
  2025-04-23 23:54             ` softworkz .
  0 siblings, 2 replies; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-23 22:34 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Tuesday 2025-04-22 21:55:31 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> Perform multiple improvements to increase code robustness.
> In particular:
> - favor unsigned counters for loops
> - add missing checks

> - avoid possibly leaks

my typo: possible leaks

> - move variable declarations to inner scopes when feasible
> - provide explicit type-casting when needed
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---

General nit about headline caseing: from the log most commit use
all lowercase in headline (I personally only use that form and at some
point everybody was using that).

>  fftools/textformat/avtextformat.c | 85 ++++++++++++++++++++-----------
>  fftools/textformat/avtextformat.h |  6 +--
>  fftools/textformat/tf_default.c   |  8 ++-
>  fftools/textformat/tf_ini.c       |  2 +-
>  fftools/textformat/tf_json.c      | 17 ++++---
>  fftools/textformat/tf_xml.c       |  3 --
>  fftools/textformat/tw_avio.c      | 11 +++-
>  7 files changed, 83 insertions(+), 49 deletions(-)
> 
> diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
> index 74d179c516..1939a1f739 100644
> --- a/fftools/textformat/avtextformat.c
> +++ b/fftools/textformat/avtextformat.c
> @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
>  
>  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++)
> +    for (unsigned i = 0; i < ubuf_size; i++)
>          av_bprintf(bp, "%02X", ubuf[i]);
>  }
>  
> @@ -137,6 +136,9 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
>      AVTextFormatContext *tctx;
>      int i, ret = 0;
>  
> +    if (!ptctx || !formatter)
> +        return AVERROR(EINVAL);
> +
>      if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
> @@ -209,25 +211,26 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
>                      av_log(NULL, AV_LOG_ERROR, " %s", n);
>                  av_log(NULL, AV_LOG_ERROR, "\n");
>              }
> -            return ret;
> +            goto fail;
>          }
>  
>      /* validate replace string */
>      {
> -        const uint8_t *p = tctx->string_validation_replacement;
> -        const uint8_t *endp = p + strlen(p);
> +        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
> +        const uint8_t *endp = p + strlen((const char *)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);
> +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>                  bprint_bytes(&bp, p0, p - p0),

Is this really needed? AUTOMATIC should be faster since it will avoid
dynamic allocation and the need to finalize (although there is no
practical need for such optimization, this still seems the simplest
possible path). Besides, an UTF8 sequence cannot be longer than a few
bytes, so possibly av_utf8_decode cannot decode more than a few bytes.

>                      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;
> +                av_bprint_finalize(&bp, NULL);
> +                goto fail;
>              }
>          }
>      }
> @@ -255,6 +258,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
>  
>  void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
>  {
> +    if (section_id < 0 || section_id >= tctx->nb_sections)
> +        return;
> +
>      tctx->level++;
>      av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
>  
> @@ -268,6 +274,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
>  
>  void avtext_print_section_footer(AVTextFormatContext *tctx)
>  {
> +    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return;
> +
>      int section_id = tctx->section[tctx->level]->id;
>      int parent_section_id = tctx->level
>          ? tctx->section[tctx->level - 1]->id
> @@ -285,7 +294,11 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
>  
>  void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
>  {
> -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
> +    const AVTextFormatSection *section;
> +
> +    av_assert0(key && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
> +
> +    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);
> @@ -354,17 +367,18 @@ struct unit_value {
>      const char *unit;
>  };
>  
> -static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
> +static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
>  {
>      double vald;
> -    int64_t vali;
> +    int64_t vali = 0;
>      int show_float = 0;
>  
>      if (uv.unit == unit_second_str) {
>          vald = uv.val.d;
>          show_float = 1;
>      } else {
> -        vald = vali = uv.val.i;
> +        vald = (double)uv.val.i;
> +        vali = uv.val.i;
>      }
>  
>      if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
> @@ -383,17 +397,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
>              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);
> +                index = (int64_t)(log2(vald) / 10);
> +                index = av_clip64(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);
> +                index = (int64_t)(log10(vald) / 3);
> +                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
>                  vald /= si_prefixes[index].dec_val;
>                  prefix_string = si_prefixes[index].dec_str;
>              }
> -            vali = vald;
> +            vali = (int64_t)vald;
>          }
>  
>          if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
> @@ -421,9 +435,13 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
>  
>  int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
>  {
> -    const struct AVTextFormatSection *section = tctx->section[tctx->level];
> +    const AVTextFormatSection *section;
>      int ret = 0;
>  
> +    av_assert0(key && val && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
> +
> +    section = tctx->section[tctx->level];
> +
>      if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
>          (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
>              && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
> @@ -465,12 +483,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
>  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);
> +        char buf[128];
> +        double d = av_q2d(*time_base) * ts;
>          struct unit_value uv;
>          uv.val.d = d;
>          uv.unit = unit_second_str;
> @@ -491,7 +508,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
>                         const uint8_t *data, int size)
>  {
>      AVBPrint bp;
> -    int offset = 0, l, i;
> +    unsigned offset = 0;
> +    int l, i;
>  
>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>      av_bprintf(&bp, "\n");
> @@ -518,25 +536,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
>  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 };
> +    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
> +    int len;
>  
>      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);
> +    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
> +    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
>      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)
> +                           uint8_t *data, int size, const char *format,
> +                           int columns, int bytes, int offset_add)
>  {
>      AVBPrint bp;
> -    int offset = 0, l, i;
> +    unsigned offset = 0;
> +    int l, i;
> +
> +    if (!name || !data || !format || columns <= 0 || bytes <= 0)
> +        return;
>  
>      av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>      av_bprintf(&bp, "\n");
> @@ -602,12 +624,15 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
>      AVTextWriterContext *wctx;
>      int ret = 0;
>  
> -    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
> +    if (!pwctx || !writer)
> +        return AVERROR(EINVAL);
> +
> +    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
>  
> -    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
> +    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
>          ret = AVERROR(ENOMEM);
>          goto fail;
>      }
> diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
> index c598af3450..aea691f351 100644
> --- a/fftools/textformat/avtextformat.h
> +++ b/fftools/textformat/avtextformat.h
> @@ -21,9 +21,7 @@
>  #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"
> @@ -103,7 +101,7 @@ struct AVTextFormatContext {
>      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];
> +    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
>      AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
>                                                    ///  used by various formatters
>  
> @@ -124,7 +122,7 @@ struct AVTextFormatContext {
>  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
>  
>  int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
> -                        const struct AVTextFormatSection *sections, int nb_sections,
> +                        const AVTextFormatSection *sections, int nb_sections,
>                          int show_value_unit,
>                          int use_value_prefix,
>                          int use_byte_value_binary_prefix,
> diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
> index 2c5047eafd..ad97173b0b 100644
> --- a/fftools/textformat/tf_default.c
> +++ b/fftools/textformat/tf_default.c
> @@ -68,9 +68,10 @@ 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;
> +    unsigned i;
> +
>      for (i = 0; src[i] && i < dst_size - 1; i++)
> -        dst[i] = av_toupper(src[i]);
> +        dst[i] = (char)av_toupper(src[i]);
>      dst[i] = 0;
>      return dst;
>  }
> @@ -106,6 +107,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
>      const struct AVTextFormatSection *section = wctx->section[wctx->level];
>      char buf[32];
>  
> +    if (!section)
> +        return;
> +
>      if (def->noprint_wrappers || def->nested_section[wctx->level])
>          return;
>  
> diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
> index 88add0819a..dd77d0e8bf 100644
> --- a/fftools/textformat/tf_ini.c
> +++ b/fftools/textformat/tf_ini.c
> @@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
>              /* fallthrough */
>          default:
>              if ((unsigned char)c < 32)
> -                av_bprintf(dst, "\\x00%02x", c & 0xff);
> +                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
>              else
>                  av_bprint_chars(dst, c, 1);
>              break;
> diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
> index b61d3740c6..50c3d90440 100644
> --- a/fftools/textformat/tf_json.c
> +++ b/fftools/textformat/tf_json.c
> @@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
>      static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
>      const char *p;
>  
> +    if (!src) {
> +        av_log(log_ctx, AV_LOG_WARNING, "Cannot escape NULL string, returning NULL\n");
> +        return NULL;
> +    }
> +
>      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);
> +            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
>          } else {
>              av_bprint_chars(dst, *p, 1);
>          }
> @@ -100,11 +105,10 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
>  {
>      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;
> +    const AVTextFormatSection *section = wctx->section[wctx->level];
> +    const AVTextFormatSection *parent_section = wctx->level ? wctx->section[wctx->level - 1] : NULL;
>  
> -    if (wctx->level && wctx->nb_item[wctx->level-1])
> +    if (wctx->level && wctx->nb_item[wctx->level - 1])
>          writer_put_str(wctx, ",\n");
>  
>      if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
> @@ -185,8 +189,7 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
>  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;
> +    const 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))
> diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
> index befb39246d..28abfc6400 100644
> --- a/fftools/textformat/tf_xml.c
> +++ b/fftools/textformat/tf_xml.c
> @@ -18,10 +18,7 @@
>   * 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"
> diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
> index 6034f74ec9..29889598bb 100644
> --- a/fftools/textformat/tw_avio.c
> +++ b/fftools/textformat/tw_avio.c
> @@ -23,6 +23,7 @@
>  #include <string.h>
>  
>  #include "avtextwriters.h"
> +#include "libavutil/avassert.h"
>  
>  #include "libavutil/error.h"
>  
> @@ -53,7 +54,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
>  static void io_put_str(AVTextWriterContext *wctx, const char *str)
>  {
>      IOWriterContext *ctx = wctx->priv;
> -    avio_write(ctx->avio_context, str, strlen(str));
> +    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
>  }
>  
>  static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> @@ -78,10 +79,14 @@ const AVTextWriter avtextwriter_avio = {
>  
>  int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
>  {
> +    if (!output_filename || !output_filename[0]) {
> +        av_log(NULL, AV_LOG_ERROR, "The output_filename cannot be NULL or empty\n");
> +        return AVERROR(EINVAL);
> +    }
> +
>      IOWriterContext *ctx;
>      int ret;
>  
> -
>      ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
>      if (ret < 0)
>          return ret;
> @@ -103,6 +108,8 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
>  
>  int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
>  {
> +    av_assert0(avio_ctx);
> +
>      IOWriterContext *ctx;
>      int ret;

In general I see some confusion about how to deal with invalid
parameters (this is probably an issue with the whole codebase), since
it's not always clear what's the right thing to do - consider that as
a programmer unrecoverable mistake and assert or let the user handle
that or just silently perform a no-op. It's probably fine to go with
the current patch and later refine in case we elaborate a set of
guidelines to apply.
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use ac_default_item_name
  2025-04-22 21:10           ` softworkz .
@ 2025-04-23 22:36             ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-23 22:36 UTC (permalink / raw)
  To: FFmpeg development discussions and patches, Stefano Sabatini



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> softworkz .
> Sent: Dienstag, 22. April 2025 23:11
> To: Stefano Sabatini <stefasab@gmail.com>; FFmpeg development
> discussions and patches <ffmpeg-devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use
> ac_default_item_name
> 
> 
> 
> > -----Original Message-----
> > From: Stefano Sabatini <stefasab@gmail.com>
> > Sent: Montag, 21. April 2025 19:31
> > To: FFmpeg development discussions and patches <ffmpeg-
> > devel@ffmpeg.org>
> > Cc: softworkz <softworkz@hotmail.com>
> > Subject: Re: [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal:
> Use
> > ac_default_item_name
> >
> > On date Sunday 2025-04-20 22:59:07 +0000, softworkz wrote:
> > > From: softworkz <softworkz@hotmail.com>
> > >
> > > Signed-off-by: softworkz <softworkz@hotmail.com>
> > > ---
> > >  fftools/textformat/tf_internal.h | 6 +-----
> > >  1 file changed, 1 insertion(+), 5 deletions(-)
> >
> > Typo in commit headling: ac_default... -> av_default...
> >
> > >
> > > diff --git a/fftools/textformat/tf_internal.h
> > b/fftools/textformat/tf_internal.h
> > > index 7b326328cb..e145bc83bb 100644
> > > --- a/fftools/textformat/tf_internal.h
> > > +++ b/fftools/textformat/tf_internal.h
> > > @@ -29,13 +29,9 @@
> > >  #include "avtextformat.h"
> > >
> > >  #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,                  \
> > > +    .item_name  = av_default_item_name,             \
> > >      .option     = name##_options                    \
> > >  }
> >
> > Looks good to me.
> 
> 
> Hi Stefano,
> 
> thanks a lot for the review.
> 
> I have applied all the suggested changes (including those where I
> didn't
> explicitly say I would).
> 
> For the avtext_context_open() function, I have introduced a new
> structure
> AVTextFormatOptions to achieve a stable function signature that
> doesn't
> need to be changed when adding new options.
> This is done in a separate commit. The other two changes where you
> mentioned
> have been moved into their own commits as well.

Yet I realized that I need to squash 05 and 06 together again, they
aren't fully independent. It compiles but fails FATE. 
The signature change (from ... to va_list) was required in order to
replace all the macro definitions (which can work with ...) with
inline functions in the new header file (tf_internal.h), where the
writer_printf() function is defined as 

writer_printf(AVTextFormatContext *wctx, const char *fmt, ...)
and hence it cannot call and forward params to another variadic
function (like writer_printf was defined before).

Thanks,
sw





_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 03/14] fftools/avtextformat: Re-use BPrint in loop
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 03/14] fftools/avtextformat: Re-use BPrint in loop softworkz
@ 2025-04-23 22:45           ` Stefano Sabatini
  0 siblings, 0 replies; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-23 22:45 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Tuesday 2025-04-22 21:55:32 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> Instead of initializing a new BPrint in each iteration of
> the loop, re-use the same BPrint struct and just clear it
> for each iteration.
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/avtextformat.c | 22 ++++++++++++----------
>  1 file changed, 12 insertions(+), 10 deletions(-)
> 
> diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
> index 1939a1f739..0a221f4a9a 100644
> --- a/fftools/textformat/avtextformat.c
> +++ b/fftools/textformat/avtextformat.c
> @@ -308,24 +308,25 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
>  
>  static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
>  {
> -    const uint8_t *p, *endp;
> +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
>      AVBPrint dstbuf;
> +    AVBPrint bp;
>      int invalid_chars_nb = 0, ret = 0;
>  
> +    *dstp = NULL;
>      av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
> +    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
>  
> -    endp = src + strlen(src);
> -    for (p = src; *p;) {
> -        uint32_t code;
> +    endp = srcp + strlen(src);
> +    for (p = srcp; *p;) {
> +        int32_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);

> +            av_bprint_clear(&bp);

> +            bprint_bytes(&bp, p0, p - p0);

cosmetical - let's keep it separated to avoid complicating the diff

> +            av_log(tctx, AV_LOG_DEBUG, "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src);

I'm not convinced this is really needed, note that the bp is stored in
the stack and is only initialized in case of decode error. We can
theoretically have more than one error in a string, but still there
should be no need to use UNLIMITED and uninit.

Also if we want to use a function scope for bp, it's best to use a
more descriptive name (e.g. bp_invalid_sequence).

>              invalid = 1;
>          }
>  
> @@ -345,7 +346,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
>          }
>  
>          if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)

> -            av_bprint_append_data(&dstbuf, p0, p-p0);
> +            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);

unrelated
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 04/14] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open()
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 04/14] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
@ 2025-04-23 22:48           ` Stefano Sabatini
  2025-04-23 22:55             ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-23 22:48 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Tuesday 2025-04-22 21:55:33 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> This allows future addition of options without
> changes to the signature of avtext_context_open().
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/ffprobe.c                 | 13 +++++++++----
>  fftools/textformat/avtextformat.c | 21 ++++++++-------------
>  fftools/textformat/avtextformat.h | 22 +++++++++++++++-------
>  3 files changed, 32 insertions(+), 24 deletions(-)
> 
> diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
> index f5c83925b9..1277b1e4f9 100644
> --- a/fftools/ffprobe.c
> +++ b/fftools/ffprobe.c
> @@ -3168,10 +3168,15 @@ int main(int argc, char **argv)
>      if (ret < 0)
>          goto end;
>  
> -    if ((ret = avtext_context_open(&tctx, f, wctx, f_args,
> -                           sections, FF_ARRAY_ELEMS(sections), show_value_unit,
> -                            use_value_prefix, use_byte_value_binary_prefix, use_value_sexagesimal_format,
> -                            show_optional_fields, show_data_hash)) >= 0) {
> +    AVTextFormatOptions tf_options = {
> +        .show_optional_fields = show_optional_fields,
> +        .show_value_unit = show_value_unit,
> +        .use_value_prefix = use_value_prefix,
> +        .use_byte_value_binary_prefix = use_byte_value_binary_prefix,
> +        .use_value_sexagesimal_format = use_value_sexagesimal_format,
> +    };
> +
> +    if ((ret = avtext_context_open(&tctx, f, wctx, f_args, sections, FF_ARRAY_ELEMS(sections), tf_options, show_data_hash)) >= 0) {
>          if (f == &avtextformatter_xml)
>              tctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;
>  
> diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
> index 0a221f4a9a..217d9da25e 100644
> --- a/fftools/textformat/avtextformat.c
> +++ b/fftools/textformat/avtextformat.c
> @@ -125,13 +125,7 @@ void avtext_context_close(AVTextFormatContext **ptctx)
>  
>  
>  int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
> -                        const 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)
> +                        const AVTextFormatSection *sections, int nb_sections, AVTextFormatOptions options, char *show_data_hash)
>  {
>      AVTextFormatContext *tctx;
>      int i, ret = 0;
> @@ -155,11 +149,11 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
>          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;
> +    tctx->show_value_unit = options.show_value_unit;
> +    tctx->use_value_prefix = options.use_value_prefix;
> +    tctx->use_byte_value_binary_prefix = options.use_byte_value_binary_prefix;
> +    tctx->use_value_sexagesimal_format = options.use_value_sexagesimal_format;
> +    tctx->show_optional_fields = options.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);
> @@ -202,7 +196,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
>          av_dict_free(&opts);
>      }
>  
> -    if (show_data_hash)
> +    if (show_data_hash) {
>          if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
>              if (ret == AVERROR(EINVAL)) {
>                  const char *n;
> @@ -213,6 +207,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
>              }
>              goto fail;
>          }
> +    }
>  
>      /* validate replace string */
>      {
> diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
> index aea691f351..05a358132e 100644
> --- a/fftools/textformat/avtextformat.h
> +++ b/fftools/textformat/avtextformat.h
> @@ -118,17 +118,25 @@ struct AVTextFormatContext {
>      unsigned int string_validation_utf8_flags;
>  };
>  
> +typedef struct AVTextFormatOptions {
> +    int show_optional_fields;
> +    int show_value_unit;
> +    int use_value_prefix;
> +    int use_byte_value_binary_prefix;
> +    int use_value_sexagesimal_format;
> +} AVTextFormatOptions;
> +
>  #define AV_TEXTFORMAT_PRINT_STRING_OPTIONAL 1
>  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
>  
> +#define AV_TEXTFORMAT_OPEN_SHOW_VALUE_UNIT               1
> +#define AV_TEXTFORMAT_OPEN_USE_VALUE_PREFIX              2
> +#define AV_TEXTFORMAT_OPEN_USE_BYTE_BINARY_PREFIX        4
> +#define AV_TEXTFORMAT_OPEN_USE_VALUE_SEXAGESIMAL_FORMAT  8
> +#define AV_TEXTFORMAT_OPEN_SHOW_OPTIONAL_FIELDS         16

Since this is a bitmap shouldn't we use a flags field directly?
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 05/14] fftools/textformat: Introduce common header and deduplicate code
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 05/14] fftools/textformat: Introduce common header and deduplicate code softworkz
@ 2025-04-23 22:49           ` Stefano Sabatini
  0 siblings, 0 replies; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-23 22:49 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Tuesday 2025-04-22 21:55:34 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/tf_compact.c  | 32 +++++-------
>  fftools/textformat/tf_default.c  | 27 +++-------
>  fftools/textformat/tf_flat.c     | 25 +++-------
>  fftools/textformat/tf_ini.c      | 24 +++------
>  fftools/textformat/tf_internal.h | 85 ++++++++++++++++++++++++++++++++
>  fftools/textformat/tf_json.c     | 35 +++++--------
>  fftools/textformat/tf_xml.c      | 35 ++++++-------
>  7 files changed, 142 insertions(+), 121 deletions(-)
>  create mode 100644 fftools/textformat/tf_internal.h

Should still be good.
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements
  2025-04-23 22:34           ` Stefano Sabatini
@ 2025-04-23 22:53             ` softworkz .
  2025-04-23 22:56               ` Nicolas George
  2025-04-23 23:54             ` softworkz .
  1 sibling, 1 reply; 130+ messages in thread
From: softworkz . @ 2025-04-23 22:53 UTC (permalink / raw)
  To: Stefano Sabatini, FFmpeg development discussions and patches

Hi Stefano,


> 
> >              if (ret < 0) {
> >                  AVBPrint bp;
> > -                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
> > +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >                  bprint_bytes(&bp, p0, p - p0),
> 
> Is this really needed? AUTOMATIC should be faster since it will avoid
> dynamic allocation and the need to finalize (although there is no
> practical need for such optimization, this still seems the simplest
> possible path). Besides, an UTF8 sequence cannot be longer than a few
> bytes, so possibly av_utf8_decode cannot decode more than a few bytes.

My understanding is as follows, please correct me when I'm wrong:

AV_BPRINT_SIZE_AUTOMATIC

Despite its name, there's automatism for anything. There's a certain 
(not exactly known) amount of (stack) memory and it doesn't ever grow.
That means in turn that after most av_brint_* invocations, you need to
check the return value and compare it to a length value to validate 
that the recent invocation didn't overflow - otherwise, things have 
gone wrong, the content of the BPrint is invalid (or at least not 
how you actually need it to be). So you need to error out.


AV_BPRINT_SIZE_UNLIMITED

The initialization like above does NOT cause any more mem allocation
than with AV_BPRINT_SIZE_AUTOMATIC. But when you approach the limit,
it will automatically allocate memory as needed, and your results 
are always as expected and you don't ever need to check and 
"chars written" return values.

The only thing that is required with AV_BPRINT_SIZE_AUTOMATICm
is to call av_bprint_finalize() to free mem that has possibly
been allocated (unless you are sure that the total length is 
always ever pretty short.


So, unless I'd misunderstand anything, I wonder in which cases, 
AV_BPRINT_SIZE_AUTOMATIC could even be a better choice than 
AV_BPRINT_SIZE_UNLIMITED ?



> In general, I see some confusion about how to deal with invalid
> parameters (this is probably an issue with the whole codebase), since
> it's not always clear what's the right thing to do - consider that as
> a programmer unrecoverable mistake and assert or let the user handle
> that or just silently perform a no-op. It's probably fine to go with
> the current patch and later refine in case we elaborate a set of
> guidelines to apply.

I do see the that confusion indeed. Did you notice my message from yesterday?

"[FFmpeg-devel] [RFC] Shaping the AVTextFormat API Surface"

It's meant as a base for discussion to get clear about all this 😉

Best,
sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 04/14] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open()
  2025-04-23 22:48           ` Stefano Sabatini
@ 2025-04-23 22:55             ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-23 22:55 UTC (permalink / raw)
  To: Stefano Sabatini, FFmpeg development discussions and patches



> -----Original Message-----
> From: Stefano Sabatini <stefasab@gmail.com>
> Sent: Donnerstag, 24. April 2025 00:49
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Cc: softworkz <softworkz@hotmail.com>
> Subject: Re: [FFmpeg-devel] [PATCH v5 04/14] fftools/textformat:
> Introduce AVTextFormatOptions for avtext_context_open()
> 
> On date Tuesday 2025-04-22 21:55:33 +0000, softworkz wrote:
> > From: softworkz <softworkz@hotmail.com>
> >
> > This allows future addition of options without
> > changes to the signature of avtext_context_open().
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  fftools/ffprobe.c                 | 13 +++++++++----
> >  fftools/textformat/avtextformat.c | 21 ++++++++-------------
> >  fftools/textformat/avtextformat.h | 22 +++++++++++++++-------
> >  3 files changed, 32 insertions(+), 24 deletions(-)
> >
> > diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
> > index f5c83925b9..1277b1e4f9 100644
> > --- a/fftools/ffprobe.c
> > +++ b/fftools/ffprobe.c
> > @@ -3168,10 +3168,15 @@ int main(int argc, char **argv)
> >      if (ret < 0)
> >          goto end;
> >
> > -    if ((ret = avtext_context_open(&tctx, f, wctx, f_args,
> > -                           sections, FF_ARRAY_ELEMS(sections),
> show_value_unit,
> > -                            use_value_prefix,
> use_byte_value_binary_prefix, use_value_sexagesimal_format,
> > -                            show_optional_fields, show_data_hash))
> >= 0) {
> > +    AVTextFormatOptions tf_options = {
> > +        .show_optional_fields = show_optional_fields,
> > +        .show_value_unit = show_value_unit,
> > +        .use_value_prefix = use_value_prefix,
> > +        .use_byte_value_binary_prefix =
> use_byte_value_binary_prefix,
> > +        .use_value_sexagesimal_format =
> use_value_sexagesimal_format,
> > +    };
> > +
> > +    if ((ret = avtext_context_open(&tctx, f, wctx, f_args,
> sections, FF_ARRAY_ELEMS(sections), tf_options, show_data_hash)) >= 0)
> {
> >          if (f == &avtextformatter_xml)
> >              tctx->string_validation_utf8_flags |=
> AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;
> >
> > diff --git a/fftools/textformat/avtextformat.c
> b/fftools/textformat/avtextformat.c
> > index 0a221f4a9a..217d9da25e 100644
> > --- a/fftools/textformat/avtextformat.c
> > +++ b/fftools/textformat/avtextformat.c
> > @@ -125,13 +125,7 @@ void avtext_context_close(AVTextFormatContext
> **ptctx)
> >
> >
> >  int avtext_context_open(AVTextFormatContext **ptctx, const
> AVTextFormatter *formatter, AVTextWriterContext *writer_context, const
> char *args,
> > -                        const 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)
> > +                        const AVTextFormatSection *sections, int
> nb_sections, AVTextFormatOptions options, char *show_data_hash)
> >  {
> >      AVTextFormatContext *tctx;
> >      int i, ret = 0;
> > @@ -155,11 +149,11 @@ int avtext_context_open(AVTextFormatContext
> **ptctx, const AVTextFormatter *form
> >          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;
> > +    tctx->show_value_unit = options.show_value_unit;
> > +    tctx->use_value_prefix = options.use_value_prefix;
> > +    tctx->use_byte_value_binary_prefix =
> options.use_byte_value_binary_prefix;
> > +    tctx->use_value_sexagesimal_format =
> options.use_value_sexagesimal_format;
> > +    tctx->show_optional_fields = options.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);
> > @@ -202,7 +196,7 @@ int avtext_context_open(AVTextFormatContext
> **ptctx, const AVTextFormatter *form
> >          av_dict_free(&opts);
> >      }
> >
> > -    if (show_data_hash)
> > +    if (show_data_hash) {
> >          if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0)
> {
> >              if (ret == AVERROR(EINVAL)) {
> >                  const char *n;
> > @@ -213,6 +207,7 @@ int avtext_context_open(AVTextFormatContext
> **ptctx, const AVTextFormatter *form
> >              }
> >              goto fail;
> >          }
> > +    }
> >
> >      /* validate replace string */
> >      {
> > diff --git a/fftools/textformat/avtextformat.h
> b/fftools/textformat/avtextformat.h
> > index aea691f351..05a358132e 100644
> > --- a/fftools/textformat/avtextformat.h
> > +++ b/fftools/textformat/avtextformat.h
> > @@ -118,17 +118,25 @@ struct AVTextFormatContext {
> >      unsigned int string_validation_utf8_flags;
> >  };
> >
> > +typedef struct AVTextFormatOptions {
> > +    int show_optional_fields;
> > +    int show_value_unit;
> > +    int use_value_prefix;
> > +    int use_byte_value_binary_prefix;
> > +    int use_value_sexagesimal_format;
> > +} AVTextFormatOptions;
> > +
> >  #define AV_TEXTFORMAT_PRINT_STRING_OPTIONAL 1
> >  #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
> >
> > +#define AV_TEXTFORMAT_OPEN_SHOW_VALUE_UNIT               1
> > +#define AV_TEXTFORMAT_OPEN_USE_VALUE_PREFIX              2
> > +#define AV_TEXTFORMAT_OPEN_USE_BYTE_BINARY_PREFIX        4
> > +#define AV_TEXTFORMAT_OPEN_USE_VALUE_SEXAGESIMAL_FORMAT  8
> > +#define AV_TEXTFORMAT_OPEN_SHOW_OPTIONAL_FIELDS         16
> 
> Since this is a bitmap shouldn't we use a flags field directly?

Oops, forgot to remove that. I replaced this by the AVTextFormatOptions
right above....

sw

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements
  2025-04-23 22:53             ` softworkz .
@ 2025-04-23 22:56               ` Nicolas George
  2025-04-23 23:04                 ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Nicolas George @ 2025-04-23 22:56 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

softworkz . (HE12025-04-23):
> Despite its name, there's automatism for anything.

Look for the word “automatic” in the C standard.

-- 
  Nicolas George
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v4 05/11] fftools/textformat: Add function avtext_print_integer_flags()
  2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 05/11] fftools/textformat: Add function avtext_print_integer_flags() softworkz
@ 2025-04-23 22:56         ` Stefano Sabatini
  0 siblings, 0 replies; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-23 22:56 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Sunday 2025-04-20 22:59:08 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> This function works analog to the avtext_print_string() which already
> has a flags parameter.
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/avtextformat.c | 21 +++++++++++++++++++++
>  fftools/textformat/avtextformat.h |  2 ++
>  2 files changed, 23 insertions(+)
> 
> diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
> index 893b11298e..d2d84c4f1d 100644
> --- a/fftools/textformat/avtextformat.c
> +++ b/fftools/textformat/avtextformat.c
> @@ -311,6 +311,27 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
>      }
>  }
>  
> +void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags)
> +{
> +    const AVTextFormatSection *section;
> +
> +    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
> +        return;
> +
> +    section = tctx->section[tctx->level];
> +

> +    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;

possibly complex logic which should be factorized if shared with
another function - this can be done through an inline function or macro?

> +
> +    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]++;
> +    }

We can make avtext_print_integer use the _flags variant to avoid code duplication.

> +}
> +
>  static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
>  {
>      const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
> diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
> index aea691f351..16cd9b214f 100644
> --- a/fftools/textformat/avtextformat.h
> +++ b/fftools/textformat/avtextformat.h
> @@ -139,6 +139,8 @@ void avtext_print_section_footer(AVTextFormatContext *tctx);
>  
>  void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
>  
> +void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags);
> +
>  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);
> -- 
> 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".
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 07/14] fftools/tf_internal: Use av_default_item_name
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 07/14] fftools/tf_internal: Use av_default_item_name softworkz
@ 2025-04-23 22:57           ` Stefano Sabatini
  0 siblings, 0 replies; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-23 22:57 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Tuesday 2025-04-22 21:55:36 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/tf_internal.h | 6 +-----
>  1 file changed, 1 insertion(+), 5 deletions(-)
> 
> diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
> index 7b326328cb..e145bc83bb 100644
> --- a/fftools/textformat/tf_internal.h
> +++ b/fftools/textformat/tf_internal.h
> @@ -29,13 +29,9 @@
>  #include "avtextformat.h"
>  
>  #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,                  \
> +    .item_name  = av_default_item_name,             \
>      .option     = name##_options                    \
>  }

Looks good to me.
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements
  2025-04-23 22:56               ` Nicolas George
@ 2025-04-23 23:04                 ` softworkz .
  2025-04-23 23:16                   ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: softworkz . @ 2025-04-23 23:04 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Nicolas George
> Sent: Donnerstag, 24. April 2025 00:57
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply
> quality improvements
> 
> softworkz . (HE12025-04-23):
> > Despite its name, there's automatism for anything.
> 
> Look for the word “automatic” in the C standard.
> 
> --
>   Nicolas George
> _______________________________________________

Ah, alright - this is alluding to the fact that you do not need
to free it. Now it makes sense. 

Thanks for the background,
sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 06/14] fftools/textformat: AVTextWriter change writer_printf signature
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 06/14] fftools/textformat: AVTextWriter change writer_printf signature softworkz
@ 2025-04-23 23:07           ` Stefano Sabatini
  0 siblings, 0 replies; 130+ messages in thread
From: Stefano Sabatini @ 2025-04-23 23:07 UTC (permalink / raw)
  To: FFmpeg development discussions and patches; +Cc: softworkz

On date Tuesday 2025-04-22 21:55:35 +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 

> Using va_list provides greater flebility

flebility typo

Also this looks more about factorization than flexibility (for an API
used directly by users the ... format is better).

> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  fftools/textformat/avtextwriters.h | 2 +-
>  fftools/textformat/tw_avio.c       | 7 ++-----
>  fftools/textformat/tw_buffer.c     | 7 ++-----
>  fftools/textformat/tw_stdout.c     | 8 ++------
>  4 files changed, 7 insertions(+), 17 deletions(-)
> 
> diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
> index 34db3f1832..fd6da747eb 100644
> --- a/fftools/textformat/avtextwriters.h
> +++ b/fftools/textformat/avtextwriters.h
> @@ -41,7 +41,7 @@ typedef struct AVTextWriter {
>      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, ...);
> +    void (*writer_printf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);

If you change the signature, change also the name (writer_vprintf) of
the callback to make it clear it takes a va_list.

>  } AVTextWriter;
>  
>  typedef struct AVTextWriterContext {
> diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
> index 29889598bb..7d52dc4cf5 100644
> --- a/fftools/textformat/tw_avio.c
> +++ b/fftools/textformat/tw_avio.c
> @@ -57,14 +57,11 @@ static void io_put_str(AVTextWriterContext *wctx, const char *str)
>      avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
>  }
>  
> -static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> +static void io_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
>  {
>      IOWriterContext *ctx = wctx->priv;
> -    va_list ap;
>  
> -    va_start(ap, fmt);
> -    avio_vprintf(ctx->avio_context, fmt, ap);
> -    va_end(ap);
> +    avio_vprintf(ctx->avio_context, fmt, vl);
>  }
>  
>  
> diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
> index f8b38414a6..f861722247 100644
> --- a/fftools/textformat/tw_buffer.c
> +++ b/fftools/textformat/tw_buffer.c
> @@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
>      av_bprintf(ctx->buffer, "%s", str);
>  }
>  
> -static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> +static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
>  {
>      BufferWriterContext *ctx = wctx->priv;
>  
> -    va_list vargs;
> -    va_start(vargs, fmt);
> -    av_vbprintf(ctx->buffer, fmt, vargs);
> -    va_end(vargs);
> +    av_vbprintf(ctx->buffer, fmt, vl);
>  }
>  
>  
> diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
> index 23de6f671f..dace55f38a 100644
> --- a/fftools/textformat/tw_stdout.c
> +++ b/fftools/textformat/tw_stdout.c
> @@ -53,13 +53,9 @@ static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
>      printf("%s", str);
>  }
>  
> -static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
> +static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
>  {
> -    va_list ap;
> -
> -    va_start(ap, fmt);
> -    vprintf(fmt, ap);
> -    va_end(ap);
> +    vprintf(fmt, vl);
>  }
>  
>  
> -- 
> 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".
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements
  2025-04-23 23:04                 ` softworkz .
@ 2025-04-23 23:16                   ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-23 23:16 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> softworkz .
> Sent: Donnerstag, 24. April 2025 01:05
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply
> quality improvements
> 
> 
> 
> > -----Original Message-----
> > From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> > Nicolas George
> > Sent: Donnerstag, 24. April 2025 00:57
> > To: FFmpeg development discussions and patches <ffmpeg-
> > devel@ffmpeg.org>
> > Subject: Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat:
> Apply
> > quality improvements
> >
> > softworkz . (HE12025-04-23):
> > > Despite its name, there's automatism for anything.
> >
> > Look for the word “automatic” in the C standard.
> >
> > --
> >   Nicolas George
> > _______________________________________________
> 
> Ah, alright - this is alluding to the fact that you do not need
> to free it. Now it makes sense.
> 
> Thanks for the background,
> sw
> _______________________________________________

It's still somewhat confusing as you'd expect that "automatic" would 
mean a behavior like you get with UNLIMITED and UNLIMITED sounds 
like it would allocate a lot of memory (even initially), even though it
doesn't. It's not big thing, though. As mentioned before, I like 
the BPrint API. Based on your reply, I gather that my description
of behavior was accurate.

Thank you
sw











_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements
  2025-04-23 22:34           ` Stefano Sabatini
  2025-04-23 22:53             ` softworkz .
@ 2025-04-23 23:54             ` softworkz .
  1 sibling, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-23 23:54 UTC (permalink / raw)
  To: Stefano Sabatini, FFmpeg development discussions and patches



> -----Original Message-----
> From: Stefano Sabatini <stefasab@gmail.com>
> Sent: Donnerstag, 24. April 2025 00:35
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Cc: softworkz <softworkz@hotmail.com>
> Subject: Re: [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply
> quality improvements
> 
> On date Tuesday 2025-04-22 21:55:31 +0000, softworkz wrote:
> > From: softworkz <softworkz@hotmail.com>
> >
> > Perform multiple improvements to increase code robustness.
> > In particular:
> > - favor unsigned counters for loops
> > - add missing checks
> 
> > - avoid possibly leaks
> 
> my typo: possible leaks

Fixed!

> 
> > - move variable declarations to inner scopes when feasible
> > - provide explicit type-casting when needed
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> 
> General nit about headline caseing: from the log most commit use
> all lowercase in headline (I personally only use that form and at some
> point everybody was using that).

Looking at the 250 most recent commit messages, about 60% have an uppercase
letter after the first colon.


> >  fftools/textformat/avtextformat.c | 85 ++++++++++++++++++++--------
> ---
> >  fftools/textformat/avtextformat.h |  6 +--
> >  fftools/textformat/tf_default.c   |  8 ++-
> >  fftools/textformat/tf_ini.c       |  2 +-
> >  fftools/textformat/tf_json.c      | 17 ++++---
> >  fftools/textformat/tf_xml.c       |  3 --
> >  fftools/textformat/tw_avio.c      | 11 +++-
> >  7 files changed, 83 insertions(+), 49 deletions(-)
> >
> > diff --git a/fftools/textformat/avtextformat.c
> b/fftools/textformat/avtextformat.c
> > index 74d179c516..1939a1f739 100644
> > --- a/fftools/textformat/avtextformat.c
> > +++ b/fftools/textformat/avtextformat.c
> > @@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
> >
> >  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++)
> > +    for (unsigned i = 0; i < ubuf_size; i++)
> >          av_bprintf(bp, "%02X", ubuf[i]);
> >  }
> >
> > @@ -137,6 +136,9 @@ int avtext_context_open(AVTextFormatContext
> **ptctx, const AVTextFormatter *form
> >      AVTextFormatContext *tctx;
> >      int i, ret = 0;
> >
> > +    if (!ptctx || !formatter)
> > +        return AVERROR(EINVAL);
> > +
> >      if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
> >          ret = AVERROR(ENOMEM);
> >          goto fail;
> > @@ -209,25 +211,26 @@ int avtext_context_open(AVTextFormatContext
> **ptctx, const AVTextFormatter *form
> >                      av_log(NULL, AV_LOG_ERROR, " %s", n);
> >                  av_log(NULL, AV_LOG_ERROR, "\n");
> >              }
> > -            return ret;
> > +            goto fail;
> >          }
> >
> >      /* validate replace string */
> >      {
> > -        const uint8_t *p = tctx->string_validation_replacement;
> > -        const uint8_t *endp = p + strlen(p);
> > +        const uint8_t *p = (uint8_t *)tctx-
> >string_validation_replacement;
> > +        const uint8_t *endp = p + strlen((const char *)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);
> > +                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> >                  bprint_bytes(&bp, p0, p - p0),
> 
> Is this really needed? AUTOMATIC should be faster since it will avoid
> dynamic allocation and the need to finalize (although there is no
> practical need for such optimization, this still seems the simplest
> possible path). Besides, an UTF8 sequence cannot be longer than a few
> bytes, so possibly av_utf8_decode cannot decode more than a few bytes.

As replied earlier, AUTOMATIC is not faster or slower than UNLIMITED
as long as the stack-allocated memory is sufficient. Only when it grows
bigger: UNLIMITED will auto-allocate as needed and AUTOMATIC will 
truncate and you'd need to check for that.

UNLIMITED gives you a "no-brain-pain-always-fine" behavior at the 
small cost of calling avbp_finalize().

sw
_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing
  2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
                           ` (13 preceding siblings ...)
  2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 14/14] fftools/graphprint: Now, make it a Killer-Feature! softworkz
@ 2025-04-24  1:12         ` ffmpegagent
  2025-04-24  1:12           ` [FFmpeg-devel] [PATCH v6 01/13] fftools/textformat: Formatting and whitespace changes softworkz
                             ` (13 more replies)
  14 siblings, 14 replies; 130+ messages in thread
From: ffmpegagent @ 2025-04-24  1:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

Shortest cover letter for my longest-running FFmpeg patchset:

 * Apply
 * Build
 * Add the "-sg" switch to any FFmpeg command line
 * Press 'q' when you don't want to wait

SG = Show Graph

Documentation and examples can be found here:

https://github.com/softworkz/ffmpeg_output_apis/wiki


Version Updates
===============


V2
==

 * Rebased on top of Andreas' improvements
 * Applied changes from review (thanks, Andreas)


V3
==

 * Fixed all "new warnings"
 * Fixed out-of-tree building (thanks, Michael)


V4
==

 * Resolved merge conflict
 * Fixed build on MinGW (missing include due to WIN32_LEAN_AND_MEAN being
   defined) (thanks, Michael)


V5
==

 * Applied changes as per review from Stefano (thanks!)
 * Introduced AVTextFormatOptions struct for options in
   avtext_context_open()


V6
==

 * Fix "new warning" in 2nd last commit
 * Squash patches 04 and 05 (they weren't truely independent)
 * Applied changes as per review from Stefano (thanks!)

.

softworkz (13):
  fftools/textformat: Formatting and whitespace changes
  fftools/textformat: Apply quality improvements
  fftools/avtextformat: Re-use BPrint in loop
  fftools/textformat: Introduce AVTextFormatOptions for
    avtext_context_open()
  fftools/textformat: Introduce common header and deduplicate code
  fftools/tf_internal: Use av_default_item_name
  fftools/textformat: Add function avtext_print_integer_flags()
  fftools/ffmpeg_filter: Move some declaration to new header file
  avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  fftools/resources: Add resource manager files
  fftools/ffmpeg_mux: Make ms_from_ost() inline
  fftools/graphprint: Add execution graph printing
  fftools/graphprint: Now, make it a Killer-Feature!

 doc/APIchanges                     |    3 +
 doc/ffmpeg.texi                    |   14 +
 ffbuild/common.mak                 |   28 +-
 fftools/Makefile                   |   22 +-
 fftools/ffmpeg.c                   |    4 +
 fftools/ffmpeg.h                   |    4 +
 fftools/ffmpeg_filter.c            |  195 +----
 fftools/ffmpeg_filter.h            |  234 ++++++
 fftools/ffmpeg_mux.h               |    2 +-
 fftools/ffmpeg_opt.c               |   17 +
 fftools/ffprobe.c                  |   13 +-
 fftools/graph/filelauncher.c       |  205 +++++
 fftools/graph/graphprint.c         | 1147 ++++++++++++++++++++++++++++
 fftools/graph/graphprint.h         |   62 ++
 fftools/resources/.gitignore       |    4 +
 fftools/resources/Makefile         |   27 +
 fftools/resources/graph.css        |  353 +++++++++
 fftools/resources/graph.html       |   86 +++
 fftools/resources/resman.c         |  213 ++++++
 fftools/resources/resman.h         |   50 ++
 fftools/textformat/avtextformat.c  |  228 +++---
 fftools/textformat/avtextformat.h  |   67 +-
 fftools/textformat/avtextwriters.h |   11 +-
 fftools/textformat/tf_compact.c    |  121 +--
 fftools/textformat/tf_default.c    |   55 +-
 fftools/textformat/tf_flat.c       |   51 +-
 fftools/textformat/tf_ini.c        |   62 +-
 fftools/textformat/tf_internal.h   |   81 ++
 fftools/textformat/tf_json.c       |   56 +-
 fftools/textformat/tf_mermaid.c    |  658 ++++++++++++++++
 fftools/textformat/tf_mermaid.h    |   41 +
 fftools/textformat/tf_xml.c        |   68 +-
 fftools/textformat/tw_avio.c       |   20 +-
 fftools/textformat/tw_buffer.c     |    9 +-
 fftools/textformat/tw_stdout.c     |   10 +-
 libavfilter/avfilter.c             |    9 +
 libavfilter/avfilter.h             |   12 +
 37 files changed, 3682 insertions(+), 560 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h
 create mode 100644 fftools/graph/filelauncher.c
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h
 create mode 100644 fftools/textformat/tf_internal.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h


base-commit: 25b0a8e295749a60a238ba0d6fe7a3742937b6bb
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-66%2Fsoftworkz%2Fsubmit_print_execution_graph-v6
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-66/softworkz/submit_print_execution_graph-v6
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/66

Range-diff vs v5:

  1:  0672fc41e7 !  1:  b4bb8cdcc6 fftools/textformat: Formatting and whitespace changes
     @@ Metadata
       ## Commit message ##
          fftools/textformat: Formatting and whitespace changes
      
     +    Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
          Signed-off-by: softworkz <softworkz@hotmail.com>
      
       ## fftools/textformat/avtextformat.c ##
  2:  1e312f4685 !  2:  1a4044ba23 fftools/textformat: Apply quality improvements
     @@ Commit message
          In particular:
          - favor unsigned counters for loops
          - add missing checks
     -    - avoid possibly leaks
     +    - avoid possible leaks
          - move variable declarations to inner scopes when feasible
          - provide explicit type-casting when needed
      
  3:  c71836fce0 !  3:  5972ecf213 fftools/avtextformat: Re-use BPrint in loop
     @@ fftools/textformat/avtextformat.c: void avtext_print_integer(AVTextFormatContext
      -    const uint8_t *p, *endp;
      +    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
           AVBPrint dstbuf;
     -+    AVBPrint bp;
     ++    AVBPrint bp_invalid_seq;
           int invalid_chars_nb = 0, ret = 0;
       
      +    *dstp = NULL;
           av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
     -+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     ++    av_bprint_init(&bp_invalid_seq, 0, AV_BPRINT_SIZE_UNLIMITED);
       
      -    endp = src + strlen(src);
      -    for (p = src; *p;) {
     @@ fftools/textformat/avtextformat.c: void avtext_print_integer(AVTextFormatContext
      -            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);
     -+            av_bprint_clear(&bp);
     -+            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);
     ++
     ++            av_bprint_clear(&bp_invalid_seq);
     ++
     ++            bprint_bytes(&bp_invalid_seq, p0, p - p0);
     ++
     ++            av_log(tctx, AV_LOG_DEBUG, "Invalid UTF-8 sequence %s found in string '%s'\n", bp_invalid_seq.str, src);
                   invalid = 1;
               }
       
     -@@ fftools/textformat/avtextformat.c: static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
     -         }
     - 
     -         if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
     --            av_bprint_append_data(&dstbuf, p0, p-p0);
     -+            av_bprint_append_data(&dstbuf, (const char *)p0, p - p0);
     -     }
     - 
     -     if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE)
      @@ fftools/textformat/avtextformat.c: static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
       
       end:
           av_bprint_finalize(&dstbuf, dstp);
     -+    av_bprint_finalize(&bp, NULL);
     ++    av_bprint_finalize(&bp_invalid_seq, NULL);
           return ret;
       }
       
  4:  26be409371 !  4:  97ab9e0426 fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open()
     @@ Commit message
          This allows future addition of options without
          changes to the signature of avtext_context_open().
      
     +    Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
          Signed-off-by: softworkz <softworkz@hotmail.com>
      
       ## fftools/ffprobe.c ##
     @@ fftools/textformat/avtextformat.h: struct AVTextFormatContext {
       #define AV_TEXTFORMAT_PRINT_STRING_OPTIONAL 1
       #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
       
     -+#define AV_TEXTFORMAT_OPEN_SHOW_VALUE_UNIT               1
     -+#define AV_TEXTFORMAT_OPEN_USE_VALUE_PREFIX              2
     -+#define AV_TEXTFORMAT_OPEN_USE_BYTE_BINARY_PREFIX        4
     -+#define AV_TEXTFORMAT_OPEN_USE_VALUE_SEXAGESIMAL_FORMAT  8
     -+#define AV_TEXTFORMAT_OPEN_SHOW_OPTIONAL_FIELDS         16
     -+
       int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
      -                        const AVTextFormatSection *sections, int nb_sections,
      -                        int show_value_unit,
  5:  89da2c883e !  5:  6d1cf2b3fd fftools/textformat: Introduce common header and deduplicate code
     @@ Metadata
       ## Commit message ##
          fftools/textformat: Introduce common header and deduplicate code
      
     +    Also change writer_printf signature in AVTextWriter to use va_list,
     +    so that it can be called by the new function writer_printf()
     +    in tf_internal.h.
     +
     +    Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
          Signed-off-by: softworkz <softworkz@hotmail.com>
      
     + ## fftools/textformat/avtextwriters.h ##
     +@@ fftools/textformat/avtextwriters.h: typedef struct AVTextWriter {
     +     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, ...);
     ++    void (*writer_vprintf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
     + } AVTextWriter;
     + 
     + typedef struct AVTextWriterContext {
     +
       ## fftools/textformat/tf_compact.c ##
      @@
       #include "libavutil/bprint.h"
     @@ fftools/textformat/tf_internal.h (new)
      +{
      +    va_list args;
      +    va_start(args, fmt);
     -+    wctx->writer->writer->writer_printf(wctx->writer, fmt, args);
     ++    wctx->writer->writer->writer_vprintf(wctx->writer, fmt, args);
      +    va_end(args);
      +}
      +
     @@ fftools/textformat/tf_xml.c: const AVTextFormatter avtextformatter_xml = {
           .priv_class           = &xml_class,
       };
      -
     +
     + ## fftools/textformat/tw_avio.c ##
     +@@ fftools/textformat/tw_avio.c: static void io_put_str(AVTextWriterContext *wctx, const char *str)
     +     avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
     + }
     + 
     +-static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
     ++static void io_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
     + {
     +     IOWriterContext *ctx = wctx->priv;
     +-    va_list ap;
     + 
     +-    va_start(ap, fmt);
     +-    avio_vprintf(ctx->avio_context, fmt, ap);
     +-    va_end(ap);
     ++    avio_vprintf(ctx->avio_context, fmt, vl);
     + }
     + 
     + 
     +@@ fftools/textformat/tw_avio.c: const AVTextWriter avtextwriter_avio = {
     +     .priv_size            = sizeof(IOWriterContext),
     +     .uninit               = iowriter_uninit,
     +     .writer_put_str       = io_put_str,
     +-    .writer_printf        = io_printf,
     ++    .writer_vprintf       = io_vprintf,
     +     .writer_w8            = io_w8
     + };
     + 
     +
     + ## fftools/textformat/tw_buffer.c ##
     +@@ fftools/textformat/tw_buffer.c: static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
     +     av_bprintf(ctx->buffer, "%s", str);
     + }
     + 
     +-static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
     ++static void buffer_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
     + {
     +     BufferWriterContext *ctx = wctx->priv;
     + 
     +-    va_list vargs;
     +-    va_start(vargs, fmt);
     +-    av_vbprintf(ctx->buffer, fmt, vargs);
     +-    va_end(vargs);
     ++    av_vbprintf(ctx->buffer, fmt, vl);
     + }
     + 
     + 
     +@@ fftools/textformat/tw_buffer.c: const AVTextWriter avtextwriter_buffer = {
     +     .priv_size            = sizeof(BufferWriterContext),
     +     .priv_class           = &bufferwriter_class,
     +     .writer_put_str       = buffer_put_str,
     +-    .writer_printf        = buffer_printf,
     ++    .writer_vprintf       = buffer_vprintf,
     +     .writer_w8            = buffer_w8
     + };
     + 
     +
     + ## fftools/textformat/tw_stdout.c ##
     +@@ fftools/textformat/tw_stdout.c: static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
     +     printf("%s", str);
     + }
     + 
     +-static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
     ++static inline void stdout_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
     + {
     +-    va_list ap;
     +-
     +-    va_start(ap, fmt);
     +-    vprintf(fmt, ap);
     +-    va_end(ap);
     ++    vprintf(fmt, vl);
     + }
     + 
     + 
     +@@ fftools/textformat/tw_stdout.c: static const AVTextWriter avtextwriter_stdout = {
     +     .priv_size            = sizeof(StdOutWriterContext),
     +     .priv_class           = &stdoutwriter_class,
     +     .writer_put_str       = stdout_put_str,
     +-    .writer_printf        = stdout_printf,
     ++    .writer_vprintf       = stdout_vprintf,
     +     .writer_w8            = stdout_w8
     + };
     + 
  6:  ecf6f061b2 <  -:  ---------- fftools/textformat: AVTextWriter change writer_printf signature
  7:  c190f79565 !  6:  fa22ead3ef fftools/tf_internal: Use av_default_item_name
     @@ Metadata
       ## Commit message ##
          fftools/tf_internal: Use av_default_item_name
      
     +    Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
          Signed-off-by: softworkz <softworkz@hotmail.com>
      
       ## fftools/textformat/tf_internal.h ##
  8:  1fe4a8fe6c !  7:  59dfd3ded6 fftools/textformat: Add function avtext_print_integer_flags()
     @@ fftools/textformat/avtextformat.c: void avtext_print_integer(AVTextFormatContext
       
      +void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags)
      +{
     -+    const AVTextFormatSection *section;
     ++    av_assert0(tctx);
      +
     -+    if (!tctx || !key || tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
     ++    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER)
      +        return;
      +
     -+    section = tctx->section[tctx->level];
     -+
     -+    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)))
     ++    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
     ++        && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
     ++        && !(tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS))
      +        return;
      +
     -+    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]++;
     -+    }
     ++    avtext_print_integer(tctx, key, val);
      +}
      +
       static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
       {
           const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
     +@@ fftools/textformat/avtextformat.c: int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
     + 
     +     section = tctx->section[tctx->level];
     + 
     +-    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)))
     ++    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER)
     ++        return 0;
     ++
     ++    if (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)) {
      
       ## fftools/textformat/avtextformat.h ##
      @@ fftools/textformat/avtextformat.h: void avtext_print_section_footer(AVTextFormatContext *tctx);
  9:  ba034ef3b1 =  8:  55a704faa5 fftools/ffmpeg_filter: Move some declaration to new header file
 10:  6e31aa603a =  9:  b6320cab8c avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
 11:  ea2d41b048 = 10:  0e3e9b3e40 fftools/resources: Add resource manager files
 12:  4fa179848a = 11:  9464b8d9f4 fftools/ffmpeg_mux: Make ms_from_ost() inline
 13:  62d4cab294 ! 12:  2b271af447 fftools/graphprint: Add execution graph printing
     @@ fftools/graph/graphprint.c (new)
      +    AVBPrint                   buf;
      +    AVTextFormatSectionContext sec_ctx = { 0 };
      +
     -+    sec_ctx.context_id = "Inputs";
     -+
      +    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
      +
      +    print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
 14:  35fc23039e ! 13:  a23cc583de fftools/graphprint: Now, make it a Killer-Feature!
     @@ fftools/graph/filelauncher.c (new)
      +}
      
       ## fftools/graph/graphprint.c ##
     -@@ fftools/graph/graphprint.c: static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifil
     -     AVBPrint                   buf;
     -     AVTextFormatSectionContext sec_ctx = { 0 };
     - 
     --    sec_ctx.context_id = "Inputs";
     --
     -     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
     - 
     -     print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
      @@ fftools/graph/graphprint.c: static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
       
           av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);

-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 01/13] fftools/textformat: Formatting and whitespace changes
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
@ 2025-04-24  1:12           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 02/13] fftools/textformat: Apply quality improvements softworkz
                             ` (12 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:12 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c  | 86 ++++++++++++++--------------
 fftools/textformat/avtextformat.h  | 16 +++---
 fftools/textformat/avtextwriters.h | 11 ++--
 fftools/textformat/tf_compact.c    | 91 +++++++++++++++++-------------
 fftools/textformat/tf_default.c    | 20 +++----
 fftools/textformat/tf_flat.c       | 26 +++++----
 fftools/textformat/tf_ini.c        | 36 ++++++------
 fftools/textformat/tf_json.c       | 10 ++--
 fftools/textformat/tf_xml.c        | 30 +++++-----
 9 files changed, 172 insertions(+), 154 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 9200b9b1ad..74d179c516 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -34,9 +34,9 @@
 #include "libavutil/opt.h"
 #include "avtextformat.h"
 
-#define SECTION_ID_NONE -1
+#define SECTION_ID_NONE (-1)
 
-#define SHOW_OPTIONAL_FIELDS_AUTO       -1
+#define SHOW_OPTIONAL_FIELDS_AUTO      (-1)
 #define SHOW_OPTIONAL_FIELDS_NEVER       0
 #define SHOW_OPTIONAL_FIELDS_ALWAYS      1
 
@@ -64,14 +64,14 @@ static const char *textcontext_get_formatter_name(void *p)
 
 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" },
+      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"}},
+      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 }
 };
 
@@ -126,7 +126,7 @@ void avtext_context_close(AVTextFormatContext **ptctx)
 
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
@@ -200,7 +200,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash) {
+    if (show_data_hash)
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -211,7 +211,6 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             return ret;
         }
-    }
 
     /* validate replace string */
     {
@@ -224,7 +223,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             if (ret < 0) {
                 AVBPrint bp;
                 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
-                bprint_bytes(&bp, p0, p-p0),
+                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);
@@ -248,15 +247,13 @@ fail:
 }
 
 /* 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_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)
+void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
@@ -272,8 +269,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx,
 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;
+    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]++;
@@ -285,8 +283,7 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
     tctx->level--;
 }
 
-void avtext_print_integer(AVTextFormatContext *tctx,
-                                        const char *key, int64_t val)
+void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
     const struct AVTextFormatSection *section = tctx->section[tctx->level];
 
@@ -324,11 +321,9 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
             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);
+                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);
@@ -340,11 +335,10 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
             av_bprint_append_data(&dstbuf, p0, p-p0);
     }
 
-    if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
+    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);
@@ -352,7 +346,11 @@ end:
 }
 
 struct unit_value {
-    union { double d; int64_t i; } val;
+    union {
+        double  d;
+        int64_t i;
+    } val;
+
     const char *unit;
 };
 
@@ -402,8 +400,9 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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 : "");
+                    prefix_string, tctx->show_value_unit ? uv.unit : "");
     }
 
     return buf;
@@ -427,8 +426,8 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 
     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)))
+            && (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)) {
@@ -440,11 +439,10 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
             if (ret < 0) goto end;
             tctx->formatter->print_string(tctx, key1, val1);
         end:
-            if (ret < 0) {
+            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 {
@@ -457,8 +455,7 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
     return ret;
 }
 
-void avtext_print_rational(AVTextFormatContext *tctx,
-                                         const char *key, AVRational q, char sep)
+void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep)
 {
     char buf[44];
     snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
@@ -466,7 +463,7 @@ void avtext_print_rational(AVTextFormatContext *tctx,
 }
 
 void avtext_print_time(AVTextFormatContext *tctx, const char *key,
-                              int64_t ts, const AVRational *time_base, int is_duration)
+                       int64_t ts, const AVRational *time_base, int is_duration)
 {
     char buf[128];
 
@@ -484,15 +481,14 @@ void avtext_print_time(AVTextFormatContext *tctx, const char *key,
 
 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)) {
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0))
         avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
-    } else {
+    else
         avtext_print_integer(tctx, key, ts);
-    }
 }
 
 void avtext_print_data(AVTextFormatContext *tctx, const char *name,
-                              const uint8_t *data, int size)
+                       const uint8_t *data, int size)
 {
     AVBPrint bp;
     int offset = 0, l, i;
@@ -520,12 +516,13 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 }
 
 void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
-                                   const uint8_t *data, int size)
+                            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));
@@ -551,7 +548,7 @@ void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
             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 --;
+            size--;
         }
         av_bprintf(&bp, "\n");
         offset += offset_add;
@@ -641,7 +638,8 @@ fail:
     return ret;
 }
 
-static const AVTextFormatter *registered_formatters[7+1];
+static const AVTextFormatter *registered_formatters[10 + 1];
+
 static void formatters_register_all(void)
 {
     static int initialized;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c2c56dc1a7..c598af3450 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -86,17 +86,17 @@ typedef struct AVTextFormatter {
 #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
+    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
+    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
+    const AVTextFormatSection *sections; ///< array containing all sections
+    int nb_sections;                   ///< number of sections
 
-    int level;                      ///< current level, starting from 0
+    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];
diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index c99d6b3548..34db3f1832 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -37,11 +37,11 @@ typedef struct AVTextWriter {
     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, ...);
+    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 {
@@ -49,7 +49,6 @@ typedef struct AVTextWriterContext {
     const AVTextWriter *writer;
     const char *name;
     void *priv;                     ///< private data for use by the writer
-
 } AVTextWriterContext;
 
 
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index 31bfc81513..d4ac296a42 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -58,10 +58,10 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 
     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 '\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)
@@ -78,6 +78,7 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 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)
@@ -114,16 +115,16 @@ typedef struct 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},
+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);
@@ -139,10 +140,13 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
     }
     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 {
+    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);
     }
@@ -162,8 +166,8 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
     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))))) {
+            (!(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 */
@@ -171,10 +175,10 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
         AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
 
         compact->nested_section[wctx->level] = 1;
-        compact->has_nested_elems[wctx->level-1] = 1;
+        compact->has_nested_elems[wctx->level - 1] = 1;
 
         av_bprintf(section_pbuf, "%s%s",
-                   wctx->section_pbuf[wctx->level-1].str, element_name);
+                   wctx->section_pbuf[wctx->level - 1].str, element_name);
 
         if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
             // add /TYPE to prefix
@@ -185,30 +189,33 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
                 char c =
                     (*p >= '0' && *p <= '9') ||
                     (*p >= 'a' && *p <= 'z') ||
-                    (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
+                    (*p >= 'A' && *p <= 'Z')
+                    ? (char)(char)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];
+        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])
+        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)))
+            !(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)
 {
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     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)))
+        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
         writer_w8(wctx, '\n');
 }
 
@@ -217,9 +224,12 @@ static void compact_print_str(AVTextFormatContext *wctx, const char *key, const
     CompactContext *compact = wctx->priv;
     AVBPrint buf;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
@@ -229,9 +239,12 @@ static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_
 {
     CompactContext *compact = wctx->priv;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
 }
 
@@ -253,15 +266,15 @@ const AVTextFormatter avtextformatter_compact = {
 #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},
+    { "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);
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 86582829e4..2c5047eafd 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -56,11 +56,11 @@ typedef struct DefaultContext {
 #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},
+    { "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);
@@ -69,7 +69,7 @@ DEFINE_FORMATTER_CLASS(default);
 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++)
+    for (i = 0; src[i] && i < dst_size - 1; i++)
         dst[i] = av_toupper(src[i]);
     dst[i] = 0;
     return dst;
@@ -85,10 +85,10 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 
     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))) {
+        !(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,
+                   wctx->section_pbuf[wctx->level - 1].str,
                    upcase_string(buf, sizeof(buf),
                                  av_x_if_null(section->element_name, section->name)));
     }
@@ -96,7 +96,7 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
     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)))
+    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));
 }
 
@@ -109,7 +109,7 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     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)))
+    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));
 }
 
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index 919d44bc6b..f692971bcc 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -57,12 +57,12 @@ typedef struct 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},
+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);
@@ -126,16 +126,18 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
     av_bprint_clear(buf);
     if (!parent_section)
         return;
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+
+    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))) {
+        !(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];
+            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);
         }
     }
@@ -166,6 +168,6 @@ const AVTextFormatter avtextformatter_flat = {
     .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,
+    .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
index d8099ff92e..88add0819a 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -64,9 +64,9 @@ typedef struct INIContext {
 #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},
+    { "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);
@@ -74,9 +74,9 @@ DEFINE_FORMATTER_CLASS(ini);
 static char *ini_escape_str(AVBPrint *dst, const char *src)
 {
     int i = 0;
-    char c = 0;
+    char c;
 
-    while (c = src[i++]) {
+    while ((c = src[i++])) {
         switch (c) {
         case '\b': av_bprintf(dst, "%s", "\\b"); break;
         case '\f': av_bprintf(dst, "%s", "\\f"); break;
@@ -84,9 +84,11 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
         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);
+        case '#':
+        case '=':
+        case ':':
+            av_bprint_chars(dst, '\\', 1);
+            /* fallthrough */
         default:
             if ((unsigned char)c < 32)
                 av_bprintf(dst, "\\x00%02x", c & 0xff);
@@ -112,23 +114,23 @@ static void ini_print_section_header(AVTextFormatContext *wctx, const void *data
         return;
     }
 
-    if (wctx->nb_item[wctx->level-1])
+    if (wctx->nb_item[wctx->level - 1])
         writer_w8(wctx, '\n');
 
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+    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))) {
+        !(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);
+            unsigned 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, ".%u", n);
         }
     }
 
-    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
         writer_printf(wctx, "[%s]\n", buf->str);
 }
 
@@ -154,6 +156,6 @@ const AVTextFormatter avtextformatter_ini = {
     .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,
+    .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
index c26a912435..b61d3740c6 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -56,9 +56,9 @@ typedef struct 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 },
+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 }
 };
 
@@ -76,8 +76,8 @@ static av_cold int json_init(AVTextFormatContext *wctx)
 
 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};
+    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++) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index 6c89d01e9d..befb39246d 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -58,11 +58,11 @@ typedef struct XMLContext {
 #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},
+    { "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);
@@ -104,8 +104,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 
         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 : "");
+                      xml->fully_qualified ? "ffprobe:" : "",
+                      xml->fully_qualified ? qual : "");
         return;
     }
 
@@ -115,12 +115,13 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
     }
 
     if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
-        wctx->level && wctx->nb_item[wctx->level-1])
+        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_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;
@@ -131,7 +132,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
         }
         writer_printf(wctx, ">\n", section->name);
     } else {
-        XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "<%s ", section->name);
         xml->within_tag = 1;
     }
 }
@@ -148,7 +150,8 @@ static void xml_print_section_footer(AVTextFormatContext *wctx)
         writer_put_str(wctx, "/>\n");
         xml->indent_level--;
     } else {
-        XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "</%s>\n", section->name);
         xml->indent_level--;
     }
 }
@@ -195,7 +198,8 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
     av_bprint_finalize(&buf, NULL);
 }
 
-static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
+static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
     xml_print_value(wctx, key, value, 0, 0);
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 02/13] fftools/textformat: Apply quality improvements
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
  2025-04-24  1:12           ` [FFmpeg-devel] [PATCH v6 01/13] fftools/textformat: Formatting and whitespace changes softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 03/13] fftools/avtextformat: Re-use BPrint in loop softworkz
                             ` (11 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Perform multiple improvements to increase code robustness.
In particular:
- favor unsigned counters for loops
- add missing checks
- avoid possible leaks
- move variable declarations to inner scopes when feasible
- provide explicit type-casting when needed

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 85 ++++++++++++++++++++-----------
 fftools/textformat/avtextformat.h |  6 +--
 fftools/textformat/tf_default.c   |  8 ++-
 fftools/textformat/tf_ini.c       |  2 +-
 fftools/textformat/tf_json.c      | 17 ++++---
 fftools/textformat/tf_xml.c       |  3 --
 fftools/textformat/tw_avio.c      | 11 +++-
 7 files changed, 83 insertions(+), 49 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 74d179c516..1939a1f739 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
 
 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++)
+    for (unsigned i = 0; i < ubuf_size; i++)
         av_bprintf(bp, "%02X", ubuf[i]);
 }
 
@@ -137,6 +136,9 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
     AVTextFormatContext *tctx;
     int i, ret = 0;
 
+    if (!ptctx || !formatter)
+        return AVERROR(EINVAL);
+
     if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
@@ -209,25 +211,26 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
                     av_log(NULL, AV_LOG_ERROR, " %s", n);
                 av_log(NULL, AV_LOG_ERROR, "\n");
             }
-            return ret;
+            goto fail;
         }
 
     /* validate replace string */
     {
-        const uint8_t *p = tctx->string_validation_replacement;
-        const uint8_t *endp = p + strlen(p);
+        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
+        const uint8_t *endp = p + strlen((const char *)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);
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
                 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;
+                av_bprint_finalize(&bp, NULL);
+                goto fail;
             }
         }
     }
@@ -255,6 +258,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
 
 void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
+    if (section_id < 0 || section_id >= tctx->nb_sections)
+        return;
+
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
 
@@ -268,6 +274,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
 
 void avtext_print_section_footer(AVTextFormatContext *tctx)
 {
+    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
     int section_id = tctx->section[tctx->level]->id;
     int parent_section_id = tctx->level
         ? tctx->section[tctx->level - 1]->id
@@ -285,7 +294,11 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
+
+    av_assert0(key && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
+
+    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);
@@ -354,17 +367,18 @@ struct unit_value {
     const char *unit;
 };
 
-static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
+static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
 {
     double vald;
-    int64_t vali;
+    int64_t vali = 0;
     int show_float = 0;
 
     if (uv.unit == unit_second_str) {
         vald = uv.val.d;
         show_float = 1;
     } else {
-        vald = vali = uv.val.i;
+        vald = (double)uv.val.i;
+        vali = uv.val.i;
     }
 
     if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
@@ -383,17 +397,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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);
+                index = (int64_t)(log2(vald) / 10);
+                index = av_clip64(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);
+                index = (int64_t)(log10(vald) / 3);
+                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
                 vald /= si_prefixes[index].dec_val;
                 prefix_string = si_prefixes[index].dec_str;
             }
-            vali = vald;
+            vali = (int64_t)vald;
         }
 
         if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
@@ -421,9 +435,13 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
 
 int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
     int ret = 0;
 
+    av_assert0(key && val && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
+
+    section = tctx->section[tctx->level];
+
     if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
         (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
             && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
@@ -465,12 +483,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
 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);
+        char buf[128];
+        double d = av_q2d(*time_base) * ts;
         struct unit_value uv;
         uv.val.d = d;
         uv.unit = unit_second_str;
@@ -491,7 +508,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
                        const uint8_t *data, int size)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -518,25 +536,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 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 };
+    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
+    int len;
 
     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);
+    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
+    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
     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)
+                           uint8_t *data, int size, const char *format,
+                           int columns, int bytes, int offset_add)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
+
+    if (!name || !data || !format || columns <= 0 || bytes <= 0)
+        return;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -602,12 +624,15 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
     AVTextWriterContext *wctx;
     int ret = 0;
 
-    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
+    if (!pwctx || !writer)
+        return AVERROR(EINVAL);
+
+    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
-    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
+    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c598af3450..aea691f351 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -21,9 +21,7 @@
 #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"
@@ -103,7 +101,7 @@ struct AVTextFormatContext {
     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];
+    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
     AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
                                                   ///  used by various formatters
 
@@ -124,7 +122,7 @@ struct AVTextFormatContext {
 #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 2c5047eafd..ad97173b0b 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -68,9 +68,10 @@ 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;
+    unsigned i;
+
     for (i = 0; src[i] && i < dst_size - 1; i++)
-        dst[i] = av_toupper(src[i]);
+        dst[i] = (char)av_toupper(src[i]);
     dst[i] = 0;
     return dst;
 }
@@ -106,6 +107,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     const struct AVTextFormatSection *section = wctx->section[wctx->level];
     char buf[32];
 
+    if (!section)
+        return;
+
     if (def->noprint_wrappers || def->nested_section[wctx->level])
         return;
 
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index 88add0819a..dd77d0e8bf 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
             /* fallthrough */
         default:
             if ((unsigned char)c < 32)
-                av_bprintf(dst, "\\x00%02x", c & 0xff);
+                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
             else
                 av_bprint_chars(dst, c, 1);
             break;
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index b61d3740c6..50c3d90440 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
     static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
     const char *p;
 
+    if (!src) {
+        av_log(log_ctx, AV_LOG_WARNING, "Cannot escape NULL string, returning NULL\n");
+        return NULL;
+    }
+
     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);
+            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
         } else {
             av_bprint_chars(dst, *p, 1);
         }
@@ -100,11 +105,10 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *parent_section = wctx->level ? wctx->section[wctx->level - 1] : NULL;
 
-    if (wctx->level && wctx->nb_item[wctx->level-1])
+    if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
     if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
@@ -185,8 +189,7 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 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;
+    const 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))
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index befb39246d..28abfc6400 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -18,10 +18,7 @@
  * 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"
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
index 6034f74ec9..29889598bb 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -23,6 +23,7 @@
 #include <string.h>
 
 #include "avtextwriters.h"
+#include "libavutil/avassert.h"
 
 #include "libavutil/error.h"
 
@@ -53,7 +54,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
 static void io_put_str(AVTextWriterContext *wctx, const char *str)
 {
     IOWriterContext *ctx = wctx->priv;
-    avio_write(ctx->avio_context, str, strlen(str));
+    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
 static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
@@ -78,10 +79,14 @@ const AVTextWriter avtextwriter_avio = {
 
 int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
 {
+    if (!output_filename || !output_filename[0]) {
+        av_log(NULL, AV_LOG_ERROR, "The output_filename cannot be NULL or empty\n");
+        return AVERROR(EINVAL);
+    }
+
     IOWriterContext *ctx;
     int ret;
 
-
     ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
     if (ret < 0)
         return ret;
@@ -103,6 +108,8 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
 
 int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
 {
+    av_assert0(avio_ctx);
+
     IOWriterContext *ctx;
     int 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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 03/13] fftools/avtextformat: Re-use BPrint in loop
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
  2025-04-24  1:12           ` [FFmpeg-devel] [PATCH v6 01/13] fftools/textformat: Formatting and whitespace changes softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 02/13] fftools/textformat: Apply quality improvements softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 04/13] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
                             ` (10 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Instead of initializing a new BPrint in each iteration of
the loop, re-use the same BPrint struct and just clear it
for each iteration.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 1939a1f739..5ba427d6f2 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -308,24 +308,28 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
 
 static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
 {
-    const uint8_t *p, *endp;
+    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
     AVBPrint dstbuf;
+    AVBPrint bp_invalid_seq;
     int invalid_chars_nb = 0, ret = 0;
 
+    *dstp = NULL;
     av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprint_init(&bp_invalid_seq, 0, AV_BPRINT_SIZE_UNLIMITED);
 
-    endp = src + strlen(src);
-    for (p = src; *p;) {
-        uint32_t code;
+    endp = srcp + strlen(src);
+    for (p = srcp; *p;) {
+        int32_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);
+
+            av_bprint_clear(&bp_invalid_seq);
+
+            bprint_bytes(&bp_invalid_seq, p0, p - p0);
+
+            av_log(tctx, AV_LOG_DEBUG, "Invalid UTF-8 sequence %s found in string '%s'\n", bp_invalid_seq.str, src);
             invalid = 1;
         }
 
@@ -355,6 +359,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
 end:
     av_bprint_finalize(&dstbuf, dstp);
+    av_bprint_finalize(&bp_invalid_seq, NULL);
     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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 04/13] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open()
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (2 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 03/13] fftools/avtextformat: Re-use BPrint in loop softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 05/13] fftools/textformat: Introduce common header and deduplicate code softworkz
                             ` (9 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This allows future addition of options without
changes to the signature of avtext_context_open().

Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffprobe.c                 | 13 +++++++++----
 fftools/textformat/avtextformat.c | 21 ++++++++-------------
 fftools/textformat/avtextformat.h | 16 +++++++++-------
 3 files changed, 26 insertions(+), 24 deletions(-)

diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index f5c83925b9..1277b1e4f9 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -3168,10 +3168,15 @@ int main(int argc, char **argv)
     if (ret < 0)
         goto end;
 
-    if ((ret = avtext_context_open(&tctx, f, wctx, f_args,
-                           sections, FF_ARRAY_ELEMS(sections), show_value_unit,
-                            use_value_prefix, use_byte_value_binary_prefix, use_value_sexagesimal_format,
-                            show_optional_fields, show_data_hash)) >= 0) {
+    AVTextFormatOptions tf_options = {
+        .show_optional_fields = show_optional_fields,
+        .show_value_unit = show_value_unit,
+        .use_value_prefix = use_value_prefix,
+        .use_byte_value_binary_prefix = use_byte_value_binary_prefix,
+        .use_value_sexagesimal_format = use_value_sexagesimal_format,
+    };
+
+    if ((ret = avtext_context_open(&tctx, f, wctx, f_args, sections, FF_ARRAY_ELEMS(sections), tf_options, show_data_hash)) >= 0) {
         if (f == &avtextformatter_xml)
             tctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;
 
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 5ba427d6f2..b8e283d00c 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -125,13 +125,7 @@ void avtext_context_close(AVTextFormatContext **ptctx)
 
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const 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)
+                        const AVTextFormatSection *sections, int nb_sections, AVTextFormatOptions options, char *show_data_hash)
 {
     AVTextFormatContext *tctx;
     int i, ret = 0;
@@ -155,11 +149,11 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         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;
+    tctx->show_value_unit = options.show_value_unit;
+    tctx->use_value_prefix = options.use_value_prefix;
+    tctx->use_byte_value_binary_prefix = options.use_byte_value_binary_prefix;
+    tctx->use_value_sexagesimal_format = options.use_value_sexagesimal_format;
+    tctx->show_optional_fields = options.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);
@@ -202,7 +196,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash)
+    if (show_data_hash) {
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -213,6 +207,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             goto fail;
         }
+    }
 
     /* validate replace string */
     {
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index aea691f351..071149c5be 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -118,17 +118,19 @@ struct AVTextFormatContext {
     unsigned int string_validation_utf8_flags;
 };
 
+typedef struct AVTextFormatOptions {
+    int show_optional_fields;
+    int show_value_unit;
+    int use_value_prefix;
+    int use_byte_value_binary_prefix;
+    int use_value_sexagesimal_format;
+} AVTextFormatOptions;
+
 #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_context, const char *args,
-                        const 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);
+                        const AVTextFormatSection *sections, int nb_sections, AVTextFormatOptions options, char *show_data_hash);
 
 void avtext_context_close(AVTextFormatContext **tctx);
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 05/13] fftools/textformat: Introduce common header and deduplicate code
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (3 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 04/13] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 06/13] fftools/tf_internal: Use av_default_item_name softworkz
                             ` (8 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Also change writer_printf signature in AVTextWriter to use va_list,
so that it can be called by the new function writer_printf()
in tf_internal.h.

Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextwriters.h |  2 +-
 fftools/textformat/tf_compact.c    | 32 ++++-------
 fftools/textformat/tf_default.c    | 27 +++-------
 fftools/textformat/tf_flat.c       | 25 +++------
 fftools/textformat/tf_ini.c        | 24 +++------
 fftools/textformat/tf_internal.h   | 85 ++++++++++++++++++++++++++++++
 fftools/textformat/tf_json.c       | 35 +++++-------
 fftools/textformat/tf_xml.c        | 35 +++++-------
 fftools/textformat/tw_avio.c       |  9 ++--
 fftools/textformat/tw_buffer.c     |  9 ++--
 fftools/textformat/tw_stdout.c     | 10 ++--
 11 files changed, 152 insertions(+), 141 deletions(-)
 create mode 100644 fftools/textformat/tf_internal.h

diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index 34db3f1832..b791bdd633 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -41,7 +41,7 @@ typedef struct AVTextWriter {
     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, ...);
+    void (*writer_vprintf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
 } AVTextWriter;
 
 typedef struct AVTextWriterContext {
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index d4ac296a42..e52888239e 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -28,23 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 
 /* Compact output */
@@ -157,9 +141,12 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
+
     compact->terminate_line[wctx->level] = 1;
     compact->has_nested_elems[wctx->level] = 0;
 
@@ -210,8 +197,11 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
 
 static void compact_print_section_footer(AVTextFormatContext *wctx)
 {
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     CompactContext *compact = wctx->priv;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (!compact->nested_section[wctx->level] &&
         compact->terminate_line[wctx->level] &&
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index ad97173b0b..019bda9d44 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -27,21 +27,7 @@
 #include "avtextformat.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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -80,9 +66,11 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(&wctx->section_pbuf[wctx->level]);
     if (parent_section &&
@@ -104,7 +92,8 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 static void default_print_section_footer(AVTextFormatContext *wctx)
 {
     DefaultContext *def = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
     char buf[32];
 
     if (!section)
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index f692971bcc..d5517f109b 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -28,22 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
-
+#include "tf_internal.h"
 
 /* Flat output */
 
@@ -118,9 +103,11 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     /* build section header */
     av_bprint_clear(buf);
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index dd77d0e8bf..8959785295 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -28,21 +28,7 @@
 
 #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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -104,9 +90,11 @@ 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(buf);
     if (!parent_section) {
diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
new file mode 100644
index 0000000000..362a4cbc38
--- /dev/null
+++ b/fftools/textformat/tf_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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
+ */
+
+/**
+ * @file
+ * Internal utilities for text formatters.
+ */
+
+#ifndef FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+#define FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+
+#include "avtextformat.h"
+
+#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                    \
+}
+
+
+/**
+ * Safely validate and access a section at a given level
+ */
+static inline const AVTextFormatSection *tf_get_section(AVTextFormatContext *tfc, int level)
+{
+    if (!tfc || level < 0 || level >= SECTION_MAX_NB_LEVELS || !tfc->section[level]) {
+        if (tfc)
+            av_log(tfc, AV_LOG_ERROR, "Invalid section access at level %d\n", level);
+        return NULL;
+    }
+    return tfc->section[level];
+}
+
+/**
+ * Safely access the parent section
+ */
+static inline const AVTextFormatSection *tf_get_parent_section(AVTextFormatContext *tfc, int level)
+{
+    if (level <= 0)
+        return NULL;
+
+    return tf_get_section(tfc, level - 1);
+}
+
+static inline void writer_w8(AVTextFormatContext *wctx, int b)
+{
+    wctx->writer->writer->writer_w8(wctx->writer, b);
+}
+
+static inline void writer_put_str(AVTextFormatContext *wctx, const char *str)
+{
+    wctx->writer->writer->writer_put_str(wctx->writer, str);
+}
+
+static inline void writer_printf(AVTextFormatContext *wctx, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    wctx->writer->writer->writer_vprintf(wctx->writer, fmt, args);
+    va_end(args);
+}
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_INTERNAL_H */
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index 50c3d90440..593d6c2947 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -27,22 +27,7 @@
 #include "avtextformat.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                    \
-}
-
+#include "tf_internal.h"
 
 /* JSON output */
 
@@ -103,10 +88,13 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
 
 static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
     AVBPrint buf;
-    const AVTextFormatSection *section = wctx->section[wctx->level];
-    const AVTextFormatSection *parent_section = wctx->level ? wctx->section[wctx->level - 1] : NULL;
+
+    if (!section)
+        return;
 
     if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
@@ -141,8 +129,11 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 
 static void json_print_section_footer(AVTextFormatContext *wctx)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         json->indent_level--;
@@ -175,9 +166,8 @@ static inline void json_print_item_str(AVTextFormatContext *wctx,
 
 static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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);
@@ -188,8 +178,8 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 
 static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const 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))
@@ -213,4 +203,3 @@ const AVTextFormatter avtextformatter_json = {
     .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
index 28abfc6400..6b09e09ab4 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -25,21 +25,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 /* XML output */
 
@@ -90,9 +76,11 @@ static av_cold int xml_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
@@ -138,7 +126,10 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 static void xml_print_section_footer(AVTextFormatContext *wctx)
 {
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
@@ -158,7 +149,10 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
 {
     AVBPrint buf;
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
 
@@ -216,4 +210,3 @@ const AVTextFormatter avtextformatter_xml = {
     .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
index 29889598bb..aeb4b5453e 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -57,14 +57,11 @@ static void io_put_str(AVTextWriterContext *wctx, const char *str)
     avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
-static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void io_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     IOWriterContext *ctx = wctx->priv;
-    va_list ap;
 
-    va_start(ap, fmt);
-    avio_vprintf(ctx->avio_context, fmt, ap);
-    va_end(ap);
+    avio_vprintf(ctx->avio_context, fmt, vl);
 }
 
 
@@ -73,7 +70,7 @@ const AVTextWriter avtextwriter_avio = {
     .priv_size            = sizeof(IOWriterContext),
     .uninit               = iowriter_uninit,
     .writer_put_str       = io_put_str,
-    .writer_printf        = io_printf,
+    .writer_vprintf       = io_vprintf,
     .writer_w8            = io_w8
 };
 
diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
index f8b38414a6..f6e63445d9 100644
--- a/fftools/textformat/tw_buffer.c
+++ b/fftools/textformat/tw_buffer.c
@@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
     av_bprintf(ctx->buffer, "%s", str);
 }
 
-static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void buffer_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     BufferWriterContext *ctx = wctx->priv;
 
-    va_list vargs;
-    va_start(vargs, fmt);
-    av_vbprintf(ctx->buffer, fmt, vargs);
-    va_end(vargs);
+    av_vbprintf(ctx->buffer, fmt, vl);
 }
 
 
@@ -72,7 +69,7 @@ const AVTextWriter avtextwriter_buffer = {
     .priv_size            = sizeof(BufferWriterContext),
     .priv_class           = &bufferwriter_class,
     .writer_put_str       = buffer_put_str,
-    .writer_printf        = buffer_printf,
+    .writer_vprintf       = buffer_vprintf,
     .writer_w8            = buffer_w8
 };
 
diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
index 23de6f671f..3e2a8dd0d4 100644
--- a/fftools/textformat/tw_stdout.c
+++ b/fftools/textformat/tw_stdout.c
@@ -53,13 +53,9 @@ static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
     printf("%s", str);
 }
 
-static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static inline void stdout_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
-    va_list ap;
-
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
+    vprintf(fmt, vl);
 }
 
 
@@ -68,7 +64,7 @@ static const AVTextWriter avtextwriter_stdout = {
     .priv_size            = sizeof(StdOutWriterContext),
     .priv_class           = &stdoutwriter_class,
     .writer_put_str       = stdout_put_str,
-    .writer_printf        = stdout_printf,
+    .writer_vprintf       = stdout_vprintf,
     .writer_w8            = stdout_w8
 };
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 06/13] fftools/tf_internal: Use av_default_item_name
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (4 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 05/13] fftools/textformat: Introduce common header and deduplicate code softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 07/13] fftools/textformat: Add function avtext_print_integer_flags() softworkz
                             ` (7 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/tf_internal.h | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
index 362a4cbc38..484886b7a7 100644
--- a/fftools/textformat/tf_internal.h
+++ b/fftools/textformat/tf_internal.h
@@ -29,13 +29,9 @@
 #include "avtextformat.h"
 
 #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,                  \
+    .item_name  = av_default_item_name,             \
     .option     = name##_options                    \
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 07/13] fftools/textformat: Add function avtext_print_integer_flags()
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (5 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 06/13] fftools/tf_internal: Use av_default_item_name softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 08/13] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
                             ` (6 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This function works analog to the avtext_print_string() which already
has a flags parameter.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 25 +++++++++++++++++++++----
 fftools/textformat/avtextformat.h |  2 ++
 2 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index b8e283d00c..86220dc676 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -301,6 +301,21 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
     }
 }
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags)
+{
+    av_assert0(tctx);
+
+    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER)
+        return;
+
+    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
+        && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
+        && !(tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS))
+        return;
+
+    avtext_print_integer(tctx, key, val);
+}
+
 static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
 {
     const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
@@ -442,10 +457,12 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 
     section = tctx->section[tctx->level];
 
-    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)))
+    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER)
+        return 0;
+
+    if (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)) {
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index 071149c5be..f4175c6f48 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -141,6 +141,8 @@ void avtext_print_section_footer(AVTextFormatContext *tctx);
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags);
+
 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);
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 08/13] fftools/ffmpeg_filter: Move some declaration to new header file
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (6 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 07/13] fftools/textformat: Add function avtext_print_integer_flags() softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 09/13] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
                             ` (5 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

to allow filtergraph printing to access the information.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_filter.c | 190 +-------------------------------
 fftools/ffmpeg_filter.h | 234 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+), 189 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index d314aec206..eab9487f97 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ffmpeg.h"
+#include "ffmpeg_filter.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -42,44 +43,6 @@
 // FIXME private header, used for mid_pred()
 #include "libavcodec/mathops.h"
 
-typedef struct FilterGraphPriv {
-    FilterGraph      fg;
-
-    // name used for logging
-    char             log_name[32];
-
-    int              is_simple;
-    // true when the filtergraph contains only meta filters
-    // that do not modify the frame data
-    int              is_meta;
-    // source filters are present in the graph
-    int              have_sources;
-    int              disable_conversions;
-
-    unsigned         nb_outputs_done;
-
-    const char      *graph_desc;
-
-    int              nb_threads;
-
-    // frame for temporarily holding output from the filtergraph
-    AVFrame         *frame;
-    // frame for sending output to the encoder
-    AVFrame         *frame_enc;
-
-    Scheduler       *sch;
-    unsigned         sch_idx;
-} FilterGraphPriv;
-
-static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
-{
-    return (FilterGraphPriv*)fg;
-}
-
-static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
-{
-    return (const FilterGraphPriv*)fg;
-}
 
 // data that is local to the filter thread and not visible outside of it
 typedef struct FilterGraphThread {
@@ -102,157 +65,6 @@ typedef struct FilterGraphThread {
     uint8_t         *eof_out;
 } FilterGraphThread;
 
-typedef struct InputFilterPriv {
-    InputFilter         ifilter;
-
-    InputFilterOptions  opts;
-
-    int                 index;
-
-    AVFilterContext    *filter;
-
-    // used to hold submitted input
-    AVFrame            *frame;
-
-    /* for filters that are not yet bound to an input stream,
-     * this stores the input linklabel, if any */
-    uint8_t            *linklabel;
-
-    // filter data type
-    enum AVMediaType    type;
-    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
-    // same as type otherwise
-    enum AVMediaType    type_src;
-
-    int                 eof;
-    int                 bound;
-    int                 drop_warned;
-    uint64_t            nb_dropped;
-
-    // parameters configured for this input
-    int                 format;
-
-    int                 width, height;
-    AVRational          sample_aspect_ratio;
-    enum AVColorSpace   color_space;
-    enum AVColorRange   color_range;
-
-    int                 sample_rate;
-    AVChannelLayout     ch_layout;
-
-    AVRational          time_base;
-
-    AVFrameSideData   **side_data;
-    int                 nb_side_data;
-
-    AVFifo             *frame_queue;
-
-    AVBufferRef        *hw_frames_ctx;
-
-    int                 displaymatrix_present;
-    int                 displaymatrix_applied;
-    int32_t             displaymatrix[9];
-
-    int                 downmixinfo_present;
-    AVDownmixInfo       downmixinfo;
-
-    struct {
-        AVFrame *frame;
-
-        int64_t last_pts;
-        int64_t end_pts;
-
-        /// marks if sub2video_update should force an initialization
-        unsigned int initialize;
-    } sub2video;
-} InputFilterPriv;
-
-static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
-{
-    return (InputFilterPriv*)ifilter;
-}
-
-typedef struct FPSConvContext {
-    AVFrame          *last_frame;
-    /* number of frames emitted by the video-encoding sync code */
-    int64_t           frame_number;
-    /* history of nb_frames_prev, i.e. the number of times the
-     * previous frame was duplicated by vsync code in recent
-     * do_video_out() calls */
-    int64_t           frames_prev_hist[3];
-
-    uint64_t          dup_warning;
-
-    int               last_dropped;
-    int               dropped_keyframe;
-
-    enum VideoSyncMethod vsync_method;
-
-    AVRational        framerate;
-    AVRational        framerate_max;
-    const AVRational *framerate_supported;
-    int               framerate_clip;
-} FPSConvContext;
-
-typedef struct OutputFilterPriv {
-    OutputFilter            ofilter;
-
-    int                     index;
-
-    void                   *log_parent;
-    char                    log_name[32];
-
-    char                   *name;
-
-    AVFilterContext        *filter;
-
-    /* desired output stream properties */
-    int                     format;
-    int                     width, height;
-    int                     sample_rate;
-    AVChannelLayout         ch_layout;
-    enum AVColorSpace       color_space;
-    enum AVColorRange       color_range;
-
-    AVFrameSideData       **side_data;
-    int                     nb_side_data;
-
-    // time base in which the output is sent to our downstream
-    // does not need to match the filtersink's timebase
-    AVRational              tb_out;
-    // at least one frame with the above timebase was sent
-    // to our downstream, so it cannot change anymore
-    int                     tb_out_locked;
-
-    AVRational              sample_aspect_ratio;
-
-    AVDictionary           *sws_opts;
-    AVDictionary           *swr_opts;
-
-    // those are only set if no format is specified and the encoder gives us multiple options
-    // They point directly to the relevant lists of the encoder.
-    const int              *formats;
-    const AVChannelLayout  *ch_layouts;
-    const int              *sample_rates;
-    const enum AVColorSpace *color_spaces;
-    const enum AVColorRange *color_ranges;
-
-    AVRational              enc_timebase;
-    int64_t                 trim_start_us;
-    int64_t                 trim_duration_us;
-    // offset for output timestamps, in AV_TIME_BASE_Q
-    int64_t                 ts_offset;
-    int64_t                 next_pts;
-    FPSConvContext          fps;
-
-    unsigned                flags;
-} OutputFilterPriv;
-
-static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
-{
-    return (OutputFilterPriv*)ofilter;
-}
-
 typedef struct FilterCommand {
     char *target;
     char *command;
diff --git a/fftools/ffmpeg_filter.h b/fftools/ffmpeg_filter.h
new file mode 100644
index 0000000000..94b94beece
--- /dev/null
+++ b/fftools/ffmpeg_filter.h
@@ -0,0 +1,234 @@
+/*
+ * 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_FFMPEG_FILTER_H
+#define FFTOOLS_FFMPEG_FILTER_H
+
+#include "ffmpeg.h"
+
+#include <stdint.h>
+
+#include "ffmpeg_sched.h"
+#include "sync_queue.h"
+
+#include "libavfilter/avfilter.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/dict.h"
+#include "libavutil/fifo.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/downmix_info.h"
+
+typedef struct FilterGraphPriv {
+    FilterGraph      fg;
+
+    // name used for logging
+    char             log_name[32];
+
+    int              is_simple;
+    // true when the filtergraph contains only meta filters
+    // that do not modify the frame data
+    int              is_meta;
+    // source filters are present in the graph
+    int              have_sources;
+    int              disable_conversions;
+
+    unsigned         nb_outputs_done;
+
+    const char      *graph_desc;
+
+    int              nb_threads;
+
+    // frame for temporarily holding output from the filtergraph
+    AVFrame         *frame;
+    // frame for sending output to the encoder
+    AVFrame         *frame_enc;
+
+    Scheduler       *sch;
+    unsigned         sch_idx;
+
+    AVBPrint graph_print_buf;
+
+} FilterGraphPriv;
+
+static inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
+{
+    return (FilterGraphPriv*)fg;
+}
+
+static inline const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
+{
+    return (const FilterGraphPriv*)fg;
+}
+
+typedef struct InputFilterPriv {
+    InputFilter         ifilter;
+
+    InputFilterOptions  opts;
+
+    int                 index;
+
+    AVFilterContext    *filter;
+
+    // used to hold submitted input
+    AVFrame            *frame;
+
+    /* for filters that are not yet bound to an input stream,
+     * this stores the input linklabel, if any */
+    uint8_t            *linklabel;
+
+    // filter data type
+    enum AVMediaType    type;
+    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
+    // same as type otherwise
+    enum AVMediaType    type_src;
+
+    int                 eof;
+    int                 bound;
+    int                 drop_warned;
+    uint64_t            nb_dropped;
+
+    // parameters configured for this input
+    int                 format;
+
+    int                 width, height;
+    AVRational          sample_aspect_ratio;
+    enum AVColorSpace   color_space;
+    enum AVColorRange   color_range;
+
+    int                 sample_rate;
+    AVChannelLayout     ch_layout;
+
+    AVRational          time_base;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
+    AVFifo             *frame_queue;
+
+    AVBufferRef        *hw_frames_ctx;
+
+    int                 displaymatrix_present;
+    int                 displaymatrix_applied;
+    int32_t             displaymatrix[9];
+
+    int                 downmixinfo_present;
+    AVDownmixInfo       downmixinfo;
+
+    struct {
+        AVFrame *frame;
+
+        int64_t last_pts;
+        int64_t end_pts;
+
+        /// marks if sub2video_update should force an initialization
+        unsigned int initialize;
+    } sub2video;
+} InputFilterPriv;
+
+static inline InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
+{
+    return (InputFilterPriv*)ifilter;
+}
+
+typedef struct FPSConvContext {
+    AVFrame          *last_frame;
+    /* number of frames emitted by the video-encoding sync code */
+    int64_t           frame_number;
+    /* history of nb_frames_prev, i.e. the number of times the
+     * previous frame was duplicated by vsync code in recent
+     * do_video_out() calls */
+    int64_t           frames_prev_hist[3];
+
+    uint64_t          dup_warning;
+
+    int               last_dropped;
+    int               dropped_keyframe;
+
+    enum VideoSyncMethod vsync_method;
+
+    AVRational        framerate;
+    AVRational        framerate_max;
+    const AVRational *framerate_supported;
+    int               framerate_clip;
+} FPSConvContext;
+
+
+typedef struct OutputFilterPriv {
+    OutputFilter            ofilter;
+
+    int                     index;
+
+    void                   *log_parent;
+    char                    log_name[32];
+
+    char                   *name;
+
+    AVFilterContext        *filter;
+
+    /* desired output stream properties */
+    int                     format;
+    int                     width, height;
+    int                     sample_rate;
+    AVChannelLayout         ch_layout;
+    enum AVColorSpace       color_space;
+    enum AVColorRange       color_range;
+
+    AVFrameSideData       **side_data;
+    int                     nb_side_data;
+
+    // time base in which the output is sent to our downstream
+    // does not need to match the filtersink's timebase
+    AVRational              tb_out;
+    // at least one frame with the above timebase was sent
+    // to our downstream, so it cannot change anymore
+    int                     tb_out_locked;
+
+    AVRational              sample_aspect_ratio;
+
+    AVDictionary           *sws_opts;
+    AVDictionary           *swr_opts;
+
+    // those are only set if no format is specified and the encoder gives us multiple options
+    // They point directly to the relevant lists of the encoder.
+    const int              *formats;
+    const AVChannelLayout  *ch_layouts;
+    const int              *sample_rates;
+    const enum AVColorSpace *color_spaces;
+    const enum AVColorRange *color_ranges;
+
+    AVRational              enc_timebase;
+    int64_t                 trim_start_us;
+    int64_t                 trim_duration_us;
+    // offset for output timestamps, in AV_TIME_BASE_Q
+    int64_t                 ts_offset;
+    int64_t                 next_pts;
+    FPSConvContext          fps;
+
+    unsigned                flags;
+} OutputFilterPriv;
+
+static inline OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
+{
+    return (OutputFilterPriv*)ofilter;
+}
+
+#endif /* FFTOOLS_FFMPEG_FILTER_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 09/13] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (7 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 08/13] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 10/13] fftools/resources: Add resource manager files softworkz
                             ` (4 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/APIchanges         |  3 +++
 libavfilter/avfilter.c |  9 +++++++++
 libavfilter/avfilter.h | 12 ++++++++++++
 3 files changed, 24 insertions(+)

diff --git a/doc/APIchanges b/doc/APIchanges
index 75d66f87f3..d0869561f3 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
+  Add avfilter_link_get_hw_frames_ctx().
+
 2025-04-21 - xxxxxxxxxx - lavu 60.2.100 - log.h
   Add AV_CLASS_CATEGORY_HWDEVICE.
 
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index 64c1075c40..c76d43a215 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -989,6 +989,15 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx)
     return pads[pad_idx].type;
 }
 
+AVBufferRef *avfilter_link_get_hw_frames_ctx(AVFilterLink *link)
+{
+    FilterLink *plink = ff_filter_link(link);
+    if (plink->hw_frames_ctx)
+        return av_buffer_ref(plink->hw_frames_ctx);
+
+    return NULL;
+}
+
 static int default_filter_frame(AVFilterLink *link, AVFrame *frame)
 {
     return ff_filter_frame(link->dst->outputs[0], frame);
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index a89d3cf658..f85929dc5c 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -96,6 +96,18 @@ const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
  */
 enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
 
+/**
+ * Get the hardware frames context of a filter link.
+ *
+ * @param link an AVFilterLink
+ *
+ * @return a ref-counted copy of the link's hw_frames_ctx field if there is
+ *         a hardware frames context associated with the link or NULL otherwise.
+ *         The returned AVBufferRef needs to be released with av_buffer_unref()
+ *         when it is no longer used.
+ */
+AVBufferRef* avfilter_link_get_hw_frames_ctx(AVFilterLink *link);
+
 /**
  * Lists of formats / etc. supported by an end of a link.
  *
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 10/13] fftools/resources: Add resource manager files
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (8 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 09/13] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 11/13] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
                             ` (3 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 ffbuild/common.mak           |  28 ++-
 fftools/Makefile             |   3 +-
 fftools/resources/.gitignore |   4 +
 fftools/resources/Makefile   |  27 +++
 fftools/resources/graph.css  | 353 +++++++++++++++++++++++++++++++++++
 fftools/resources/graph.html |  86 +++++++++
 fftools/resources/resman.c   | 213 +++++++++++++++++++++
 fftools/resources/resman.h   |  50 +++++
 8 files changed, 762 insertions(+), 2 deletions(-)
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h

diff --git a/ffbuild/common.mak b/ffbuild/common.mak
index ca45a0f368..6717092d44 100644
--- a/ffbuild/common.mak
+++ b/ffbuild/common.mak
@@ -139,6 +139,32 @@ else
 	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
 endif
 
+# 1) Preprocess CSS to a minified version
+%.css.min: %.css
+	# Must start with a tab in the real Makefile
+	sed 's!/\\*.*\\*/!!g' $< \
+	| tr '\n' ' ' \
+	| tr -s ' ' \
+	| sed 's/^ //; s/ $$//' \
+	> $@
+
+# 2) Gzip the minified CSS
+%.css.min.gz: %.css.min
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) >$@
+
+# 3) Convert the gzipped CSS to a .c array
+%.css.c: %.css.min.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
+# 4) Gzip the HTML file (no minification needed)
+%.html.gz: TAG = GZIP
+%.html.gz: %.html
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) > $@
+
+# 5) Convert the gzipped HTML to a .c array
+%.html.c: %.html.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
 clean::
 	$(RM) $(BIN2CEXE) $(CLEANSUFFIXES:%=ffbuild/%)
 
@@ -214,7 +240,7 @@ $(TOOLOBJS): | tools
 
 OUTDIRS := $(OUTDIRS) $(dir $(OBJS) $(HOBJS) $(HOSTOBJS) $(SLIBOBJS) $(SHLIBOBJS) $(STLIBOBJS) $(TESTOBJS))
 
-CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
+CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *.html.gz *.html.c *.css.gz *.css.c  *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
 LIBSUFFIXES       = *.a *.lib *.so *.so.* *.dylib *.dll *.def *.dll.a
 
 define RULES
diff --git a/fftools/Makefile b/fftools/Makefile
index e9c9891c34..a30bec889e 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -42,7 +42,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -56,6 +56,7 @@ all: $(AVPROGS)
 fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
+OUTDIRS += fftools/resources
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/resources/.gitignore b/fftools/resources/.gitignore
new file mode 100644
index 0000000000..5f496535a6
--- /dev/null
+++ b/fftools/resources/.gitignore
@@ -0,0 +1,4 @@
+*.html.c
+*.css.c
+*.html.gz
+*.css.gz
diff --git a/fftools/resources/Makefile b/fftools/resources/Makefile
new file mode 100644
index 0000000000..f3a0d0a970
--- /dev/null
+++ b/fftools/resources/Makefile
@@ -0,0 +1,27 @@
+clean::
+	$(RM) $(CLEANSUFFIXES:%=fftools/resources/%)
+
+
+HTML_RESOURCES := fftools/resources/graph.html \
+
+# .html => (gzip) .html.gz => (bin2c) .html.c => (cc) .o
+HTML_RESOURCES_GZ := $(HTML_RESOURCES:.html=.html.gz)
+HTML_RESOURCES_C := $(HTML_RESOURCES_GZ:.html.gz=.html.c)
+HTML_RESOURCES_OBJS := $(HTML_RESOURCES_C:.c=.o)
+
+CSS_RESOURCES := fftools/resources/graph.css   \
+
+# .css => (sh) .css.min  => (gzip) .css.min.gz => (bin2c) .css.c => (cc) .o
+CSS_RESOURCES_MIN := $(CSS_RESOURCES:.css=.css.min)
+CSS_RESOURCES_GZ := $(CSS_RESOURCES_MIN:.css.min=.css.min.gz)
+CSS_RESOURCES_C := $(CSS_RESOURCES_GZ:.css.min.gz=.css.c)
+CSS_RESOURCES_OBJS := $(CSS_RESOURCES_C:.c=.o)
+
+# Uncomment to prevent deletion
+#.PRECIOUS: %.css.c %.css.min %.css.gz  %.css.min.gz
+
+OBJS-resman +=                  \
+    fftools/resources/resman.o          \
+    $(HTML_RESOURCES_OBJS)      \
+    $(CSS_RESOURCES_OBJS)       \
+
diff --git a/fftools/resources/graph.css b/fftools/resources/graph.css
new file mode 100644
index 0000000000..ab480673ab
--- /dev/null
+++ b/fftools/resources/graph.css
@@ -0,0 +1,353 @@
+/* Variables */
+.root {
+    --ff-colvideo: #6eaa7b;
+    --ff-colaudio: #477fb3;
+    --ff-colsubtitle: #ad76ab;
+    --ff-coltext: #666;
+}
+
+/* Common & Misc */
+.ff-inputfiles rect, .ff-outputfiles rect, .ff-inputstreams rect, .ff-outputstreams rect, .ff-decoders rect, .ff-encoders rect {
+    stroke-width: 0;
+    stroke: transparent;
+    filter: none !important;
+    fill: transparent !important;
+    display: none !important;
+}
+
+.cluster span {
+    color: var(--ff-coltext);
+}
+
+.cluster rect {
+    stroke: #dfdfdf !important;
+    transform: translateY(-2.3rem);
+    filter: drop-shadow(1px 2px 2px rgba(185,185,185,0.2)) !important;
+    rx: 8;
+    ry: 8;
+}
+
+.cluster-label {
+    font-size: 1.1rem;
+}
+
+    .cluster-label .nodeLabel {
+        display: block;
+        font-weight: 500;
+        color: var(--ff-coltext);
+    }
+
+    .cluster-label div {
+        max-width: unset !important;
+        padding: 3px;
+    }
+
+    .cluster-label foreignObject {
+        transform: translateY(-0.7rem);
+    }
+
+/* Input and output files */
+.node.ff-inputfile .label foreignObject, .node.ff-outputfile .label foreignObject {
+    overflow: visible;
+}
+
+.cluster.ff-inputfile .cluster-label foreignObject div:not(foreignObject div div), .cluster.ff-outputfile .cluster-label foreignObject div:not(foreignObject div div) {
+    display: table !important;
+}
+
+.nodeLabel div.ff-inputfile, .nodeLabel div.ff-outputfile {
+    font-size: 1.1rem;
+    font-weight: 500;
+    min-width: 14rem;
+    width: 100%;
+    display: flex;
+    color: var(--ff-coltext);
+    margin-top: 0.1rem;
+    line-height: 1.35;
+    padding-bottom: 1.9rem;
+}
+
+.nodeLabel div.ff-outputfile {
+    flex-direction: row-reverse;
+}
+
+.ff-inputfile .index, .ff-outputfile .index {
+    order: 2;
+    color: var(--ff-coltext);
+    text-align: center;
+    border-radius: 0.45rem;
+    border: 0.18em solid #666666db;
+    font-weight: 600;
+    padding: 0 0.3em;
+    opacity: 0.8;
+}
+
+    .ff-inputfile .index::before {
+        content: 'In ';
+    }
+
+    .ff-outputfile .index::before {
+        content: 'Out ';
+    }
+
+.ff-inputfile .demuxer_name, .ff-outputfile .muxer_name {
+    flex: 1;
+    order: 1;
+    font-size: 0.9rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: center;
+    max-width: 8rem;
+    align-content: center;
+    margin: 0.2rem 0.4rem 0 0.4rem;
+}
+
+.ff-inputfile .file_extension, .ff-outputfile .file_extension {
+    order: 0;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.45rem;
+    font-weight: 600;
+    padding: 0 0.4em;
+    align-content: center;
+    opacity: 0.8;
+}
+
+.ff-inputfile .url, .ff-outputfile .url {
+    order: 4;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0.75rem;
+    font-size: 0.7rem;
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin: 0 0.3rem;
+    direction: rtl;
+    color: #999;
+}
+
+.cluster.ff-inputfile rect, .cluster.ff-outputfile rect {
+    transform: translateY(-1.8rem);
+    fill: url(#ff-radgradient);
+}
+
+/* Input and output streams */
+.node.ff-inputstream rect, .node.ff-outputstream rect {
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    fill: white;
+    stroke: #e5e5e5 !important;
+    height: 2.7rem;
+    transform: translateY(0.2rem);
+    filter: none;
+    rx: 3;
+    ry: 3;
+}
+
+.node.ff-inputstream .label foreignObject, .node.ff-outputstream .label foreignObject {
+    transform: translateY(-0.2%);
+    overflow: visible;
+}
+
+    .node.ff-inputstream .label foreignObject div:not(foreignObject div div), .node.ff-outputstream .label foreignObject div:not(foreignObject div div) {
+        display: block !important;
+        line-height: 1.5 !important;
+    }
+
+.nodeLabel div.ff-inputstream, .nodeLabel div.ff-outputstream {
+    font-size: 1.0rem;
+    font-weight: 500;
+    min-width: 12rem;
+    width: 100%;
+    display: flex;
+}
+
+.nodeLabel div.ff-outputstream {
+    flex-direction: row-reverse;
+}
+
+.ff-inputstream .name, .ff-outputstream .name {
+    flex: 1;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: left;
+    align-content: center;
+    margin-bottom: -0.15rem;
+}
+
+.ff-inputstream .index, .ff-outputstream .index {
+    flex: 0 0 1.4rem;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.3rem;
+    font-weight: 600;
+    margin-right: -0.3rem;
+    margin-left: 0.4rem;
+    opacity: 0.8;
+}
+
+.ff-outputstream .index {
+    margin-right: 0.6rem;
+    margin-left: -0.4rem;
+}
+
+.ff-inputstream::before, .ff-outputstream::before {
+    font-variant-emoji: text;
+    flex: 0 0 2rem;
+    margin-left: -0.8rem;
+    margin-right: 0.2rem;
+}
+
+.ff-outputstream::before {
+    margin-left: 0.2rem;
+    margin-right: -0.6rem;
+}
+
+.ff-inputstream.video::before, .ff-outputstream.video::before {
+    content: '\239A';
+    color: var(--ff-colvideo);
+    font-size: 2.25rem;
+    line-height: 0.5;
+    font-weight: bold;
+}
+
+.ff-inputstream.audio::before, .ff-outputstream.audio::before {
+    content: '\1F39D';
+    color: var(--ff-colaudio);
+    font-size: 1.75rem;
+    line-height: 0.9;
+}
+
+.ff-inputstream.subtitle::before, .ff-outputstream.subtitle::before {
+    content: '\1AC';
+    color: var(--ff-colsubtitle);
+    font-size: 1.2rem;
+    line-height: 1.1;
+    transform: scaleX(1.5);
+    margin-top: 0.050rem;
+}
+
+.ff-inputstream.attachment::before, .ff-outputstream.attachment::before {
+    content: '\1F4CE';
+    font-size: 1.3rem;
+    line-height: 1.15;
+}
+
+.ff-inputstream.data::before, .ff-outputstream.data::before {
+    content: '\27E8\2219\2219\2219\27E9';
+    font-size: 1.15rem;
+    line-height: 1.17;
+    letter-spacing: -0.3px;
+}
+
+/* Filter Graphs */
+.cluster.ff-filters rect {
+    stroke-dasharray: 6 !important;
+    stroke-width: 1.3px;
+    stroke: #d1d1d1 !important;
+    filter: none !important;
+}
+
+.cluster.ff-filters div.ff-filters .id {
+    display: none;
+}
+
+.cluster.ff-filters div.ff-filters .name {
+    margin-right: 0.5rem;
+    font-size: 0.9rem;
+}
+
+.cluster.ff-filters div.ff-filters .description {
+    font-weight: 400;
+    font-size: 0.75rem;
+    vertical-align: middle;
+    color: #777;
+    font-family: Cascadia Code, Lucida Console, monospace;
+}
+
+/* Filter Shapes */
+.node.ff-filter rect {
+    rx: 10;
+    ry: 10;
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.node.ff-filter .label foreignObject {
+    transform: translateY(-0.4rem);
+    overflow: visible;
+}
+
+.nodeLabel div.ff-filter {
+    font-size: 1.0rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    min-width: 5.5rem;
+    margin-bottom: 0.5rem;
+}
+
+    .nodeLabel div.ff-filter span {
+        color: inherit;
+    }
+
+/* Decoders & Encoders */
+.node.ff-decoder rect, .node.ff-encoder rect {
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.nodeLabel div.ff-decoder, .nodeLabel div.ff-encoder {
+    font-size: 0.85rem;
+    font-weight: 500;
+    min-width: 3.5rem;
+}
+
+/* Links and Arrows */
+path.flowchart-link[id|='video'] {
+    stroke: var(--ff-colvideo);
+}
+
+path.flowchart-link[id|='audio'] {
+    stroke: var(--ff-colaudio);
+}
+
+path.flowchart-link[id|='subtitle'] {
+    stroke: var(--ff-colsubtitle);
+}
+
+marker.marker path {
+    fill: context-stroke;
+}
+
+.edgeLabel foreignObject {
+    transform: translateY(-1rem);
+}
+
+.edgeLabel p {
+    background: transparent;
+    white-space: nowrap;
+    margin: 1rem 0.5rem !important;
+    font-weight: 500;
+    color: var(--ff-coltext);
+}
+
+.edgeLabel, .labelBkg {
+    background: transparent;
+}
+
+.edgeLabels .edgeLabel * {
+    font-size: 0.8rem;
+}
diff --git a/fftools/resources/graph.html b/fftools/resources/graph.html
new file mode 100644
index 0000000000..cd94276fd4
--- /dev/null
+++ b/fftools/resources/graph.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <title>FFmpeg Graph</title>
+</head>
+<body>
+<style>
+    html {
+        color: #666;
+        font-family: Roboto;
+        height: 100%;
+    }
+
+    body {
+        background-color: #f9f9f9;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        margin: 0;
+        padding: 1.7rem 1.7rem 3.5rem 1.7rem;
+    }
+
+    div#banner {
+        align-items: center;
+        display: flex;
+        flex-direction: row;
+        margin-bottom: 1.5rem;
+        margin-left: 0.6vw;
+    }
+
+    div#header {
+        aspect-ratio: 1/1;
+        background-image: url(https://trac.ffmpeg.org/ffmpeg-logo.png);
+        background-size: cover;
+        width: 1.6rem;
+    }
+
+    h1 {
+        font-size: 1.2rem;
+        margin: 0 0.5rem;
+    }
+
+    pre.mermaid {
+        align-items: center;
+        background-color: white;
+        box-shadow: 2px 2px 25px 0px #00000010;
+        color: transparent;
+        display: flex;
+        flex: 1;
+        justify-content: center;
+        margin: 0;
+        overflow: overlay;
+    }
+
+    pre.mermaid svg {
+        height: auto;
+        margin: 0;
+        max-width: unset !important;
+        width: auto;
+    }
+
+    pre.mermaid svg * {
+        user-select: none;
+    }
+</style>
+<div id="banner">
+    <div id="header"></div>
+    <h1>FFmpeg Execution Graph</h1>
+</div>
+<pre class="mermaid">
+__###__
+</pre>
+<script type="module">
+        import vanillaJsWheelZoom from 'https://cdn.jsdelivr.net/npm/vanilla-js-wheel-zoom@9.0.4/+esm';
+        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
+        function initViewer() {
+            var element = document.querySelector('.mermaid svg')
+            vanillaJsWheelZoom.create('pre.mermaid svg', { type: 'html', smoothTimeDrag: 0, width: element.clientWidth, height: element.clientHeight, maxScale: 3 });
+        }
+        mermaid.initialize({ startOnLoad: false }); 
+        document.fonts.ready.then(() => { mermaid.run({ querySelector: '.mermaid', postRenderCallback: initViewer }); });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/fftools/resources/resman.c b/fftools/resources/resman.c
new file mode 100644
index 0000000000..488aaeecf6
--- /dev/null
+++ b/fftools/resources/resman.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <zlib.h>
+#include "resman.h"
+#include <libavformat/url.h>
+#include "fftools/ffmpeg_filter.h"
+#include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+
+extern const unsigned char ff_graph_html_data[];
+extern const unsigned int ff_graph_html_len;
+
+extern const unsigned char ff_graph_css_data[];
+extern const unsigned ff_graph_css_len;
+
+static const FFResourceDefinition resource_definitions[] = {
+    [FF_RESOURCE_GRAPH_CSS]   = { FF_RESOURCE_GRAPH_CSS,   "graph.css",   &ff_graph_css_data[0],   &ff_graph_css_len   },
+    [FF_RESOURCE_GRAPH_HTML]  = { FF_RESOURCE_GRAPH_HTML,  "graph.html",  &ff_graph_html_data[0],  &ff_graph_html_len  },
+};
+
+
+static const AVClass resman_class = {
+    .class_name = "ResourceManager",
+};
+
+typedef struct ResourceManagerContext {
+    const AVClass *class;
+    AVDictionary *resource_dic;
+} ResourceManagerContext;
+
+static AVMutex mutex = AV_MUTEX_INITIALIZER;
+
+ResourceManagerContext *resman_ctx = NULL;
+
+
+static int decompress_gzip(ResourceManagerContext *ctx, uint8_t *in, unsigned in_len, char **out, size_t *out_len)
+{
+    z_stream strm;
+    unsigned chunk = 65534;
+    int ret;
+    uint8_t *buf;
+
+    *out = NULL;
+    memset(&strm, 0, sizeof(strm));
+
+    // Allocate output buffer with extra byte for null termination
+    buf = (uint8_t *)av_mallocz(chunk + 1);
+    if (!buf) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to allocate decompression buffer\n");
+        return AVERROR(ENOMEM);
+    }
+
+    // 15 + 16 tells zlib to detect GZIP or zlib automatically
+    ret = inflateInit2(&strm, 15 + 16);
+    if (ret != Z_OK) {
+        av_log(ctx, AV_LOG_ERROR, "Error during zlib initialization: %s\n", strm.msg);
+        av_free(buf);
+        return AVERROR(ENOSYS);
+    }
+
+    strm.avail_in  = in_len;
+    strm.next_in   = in;
+    strm.avail_out = chunk;
+    strm.next_out  = buf;
+
+    ret = inflate(&strm, Z_FINISH);
+    if (ret != Z_OK && ret != Z_STREAM_END) {
+        av_log(ctx, AV_LOG_ERROR, "Inflate failed: %d, %s\n", ret, strm.msg);
+        inflateEnd(&strm);
+        av_free(buf);
+        return (ret == Z_STREAM_END) ? Z_OK : ((ret == Z_OK) ? Z_BUF_ERROR : ret);
+    }
+
+    if (strm.avail_out == 0) {
+        // TODO: Error or loop decoding?
+        av_log(ctx, AV_LOG_WARNING, "Decompression buffer may be too small\n");
+    }
+
+    *out_len = chunk - strm.avail_out;
+    buf[*out_len] = 0; // Ensure null termination
+
+    inflateEnd(&strm);
+    *out = (char *)buf;
+    return Z_OK;
+}
+
+static ResourceManagerContext *get_resman_context(void)
+{
+    ResourceManagerContext *res = resman_ctx;
+
+    ff_mutex_lock(&mutex);
+
+    if (res)
+        goto end;
+
+    res = av_mallocz(sizeof(ResourceManagerContext));
+    if (!res) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to allocate resource manager context\n");
+        goto end;
+    }
+
+    res->class = &resman_class;
+    resman_ctx = res;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
+
+
+void ff_resman_uninit(void)
+{
+    ff_mutex_lock(&mutex);
+
+    if (resman_ctx) {
+        if (resman_ctx->resource_dic)
+            av_dict_free(&resman_ctx->resource_dic);
+        av_freep(&resman_ctx);
+    }
+
+    ff_mutex_unlock(&mutex);
+}
+
+
+char *ff_resman_get_string(FFResourceId resource_id)
+{
+    ResourceManagerContext *ctx               = get_resman_context();
+    FFResourceDefinition resource_definition = { 0 };
+    AVDictionaryEntry *dic_entry;
+    char *res = NULL;
+
+    if (!ctx)
+        return NULL;
+
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(resource_definitions); ++i) {
+        FFResourceDefinition def = resource_definitions[i];
+        if (def.resource_id == resource_id) {
+            resource_definition = def;
+            break;
+        }
+    }
+
+    if (!resource_definition.name) {
+        av_log(ctx, AV_LOG_ERROR, "Unable to find resource with ID %d\n", resource_id);
+        return NULL;
+    }
+
+    ff_mutex_lock(&mutex);
+
+    dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+    if (!dic_entry) {
+        char *out = NULL;
+        size_t out_len;
+        int dict_ret;
+
+        int ret = decompress_gzip(ctx, (uint8_t *)resource_definition.data, *resource_definition.data_len, &out, &out_len);
+
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to decompress the resource with ID %d\n", resource_id);
+            goto end;
+        }
+
+        dict_ret = av_dict_set(&ctx->resource_dic, resource_definition.name, out, 0);
+        if (dict_ret < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to store decompressed resource in dictionary: %d\n", dict_ret);
+            av_freep(&out);
+            goto end;
+        }
+
+        av_freep(&out);
+        dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+        if (!dic_entry) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to retrieve resource from dictionary after storing it\n");
+            goto end;
+        }
+    }
+
+    res = dic_entry->value;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
diff --git a/fftools/resources/resman.h b/fftools/resources/resman.h
new file mode 100644
index 0000000000..6485db5091
--- /dev/null
+++ b/fftools/resources/resman.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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_RESOURCES_RESMAN_H
+#define FFTOOLS_RESOURCES_RESMAN_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "fftools/ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+#include "fftools/textformat/avtextformat.h"
+
+typedef enum {
+    FF_RESOURCE_GRAPH_CSS,
+    FF_RESOURCE_GRAPH_HTML,
+} FFResourceId;
+
+typedef struct FFResourceDefinition {
+    FFResourceId resource_id;
+    const char *name;
+
+    const unsigned char *data;
+    const unsigned *data_len;
+
+} FFResourceDefinition;
+
+void ff_resman_uninit(void);
+
+char *ff_resman_get_string(FFResourceId resource_id);
+
+#endif /* FFTOOLS_RESOURCES_RESMAN_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 11/13] fftools/ffmpeg_mux: Make ms_from_ost() inline
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (9 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 10/13] fftools/resources: Add resource manager files softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 12/13] fftools/graphprint: Add execution graph printing softworkz
                             ` (2 subsequent siblings)
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_mux.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fftools/ffmpeg_mux.h b/fftools/ffmpeg_mux.h
index f41f2c18fa..4ca8ab73a4 100644
--- a/fftools/ffmpeg_mux.h
+++ b/fftools/ffmpeg_mux.h
@@ -123,7 +123,7 @@ typedef struct Muxer {
 
 int mux_check_init(void *arg);
 
-static MuxStream *ms_from_ost(OutputStream *ost)
+static inline MuxStream *ms_from_ost(OutputStream *ost)
 {
     return (MuxStream*)ost;
 }
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 12/13] fftools/graphprint: Add execution graph printing
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (10 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 11/13] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-25 22:26             ` Michael Niedermayer
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 13/13] fftools/graphprint: Now, make it a Killer-Feature! softworkz
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
  13 siblings, 1 reply; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

The key benefits are:

- Different to other graph printing methods, this is outputting:
  - all graphs with runtime state
    (including auto-inserted filters)
  - each graph with its inputs and outputs
  - all filters with their in- and output pads
  - all connections between all input- and output pads
  - for each connection:
    - the runtime-negotiated format and media type
    - the hw context
    - if video hw context, both: hw pixfmt + sw pixfmt
- Output can either be printed to stdout or written to specified file
- Output is machine-readable
- Use the same output implementation as ffprobe, supporting multiple
  formats

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi                   |   10 +
 fftools/Makefile                  |   20 +-
 fftools/ffmpeg.c                  |    4 +
 fftools/ffmpeg.h                  |    3 +
 fftools/ffmpeg_filter.c           |    5 +
 fftools/ffmpeg_opt.c              |   13 +
 fftools/graph/graphprint.c        | 1101 +++++++++++++++++++++++++++++
 fftools/graph/graphprint.h        |   30 +
 fftools/textformat/avtextformat.c |    2 +
 fftools/textformat/avtextformat.h |   29 +
 fftools/textformat/tf_mermaid.c   |  658 +++++++++++++++++
 fftools/textformat/tf_mermaid.h   |   41 ++
 12 files changed, 1915 insertions(+), 1 deletion(-)
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 17ba876ea3..35675b5309 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1394,6 +1394,16 @@ It is on by default, to explicitly disable it you need to specify @code{-nostats
 @item -stats_period @var{time} (@emph{global})
 Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
 
+@item -print_graphs (@emph{global})
+Prints execution graph details to stderr in the format set via -print_graphs_format.
+
+@item -print_graphs_file @var{filename} (@emph{global})
+Writes execution graph details to the specified file in the format set via -print_graphs_format.
+
+@item -print_graphs_format @var{format} (@emph{global})
+Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
+The default format is json.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index a30bec889e..361a4fd574 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -9,6 +9,8 @@ AVBASENAMES  = ffmpeg ffplay ffprobe
 ALLAVPROGS   = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
 ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
 
+include $(SRC_PATH)/fftools/resources/Makefile
+
 OBJS-ffmpeg +=                  \
     fftools/ffmpeg_dec.o        \
     fftools/ffmpeg_demux.o      \
@@ -19,8 +21,21 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
+    fftools/graph/graphprint.o        \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
+    fftools/textformat/avtextformat.o \
+    fftools/textformat/tf_compact.o   \
+    fftools/textformat/tf_default.o   \
+    fftools/textformat/tf_flat.o      \
+    fftools/textformat/tf_ini.o       \
+    fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
+    fftools/textformat/tf_xml.o       \
+    fftools/textformat/tw_avio.o      \
+    fftools/textformat/tw_buffer.o    \
+    fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffprobe +=                       \
     fftools/textformat/avtextformat.o \
@@ -29,10 +44,12 @@ OBJS-ffprobe +=                       \
     fftools/textformat/tf_flat.o      \
     fftools/textformat/tf_ini.o       \
     fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
     fftools/textformat/tf_xml.o       \
     fftools/textformat/tw_avio.o      \
     fftools/textformat/tw_buffer.o    \
     fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffplay += fftools/ffplay_renderer.o
 
@@ -42,7 +59,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources fftools/graph
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -57,6 +74,7 @@ fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
 OUTDIRS += fftools/resources
+OUTDIRS += fftools/graph
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..6766ec209c 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -81,6 +81,7 @@
 #include "ffmpeg.h"
 #include "ffmpeg_sched.h"
 #include "ffmpeg_utils.h"
+#include "graph/graphprint.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
+    if (print_graphs || print_graphs_file)
+        print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
+
     if (do_benchmark) {
         int64_t maxrss = getmaxrss() / 1024;
         av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 5869979214..7fbf0ad532 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -717,6 +717,9 @@ extern float max_error_rate;
 extern char *filter_nbthreads;
 extern int filter_complex_nbthreads;
 extern int vstats_version;
+extern int print_graphs;
+extern char *print_graphs_file;
+extern char *print_graphs_format;
 extern int auto_conversion_filters;
 
 extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index eab9487f97..b774606562 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -22,6 +22,7 @@
 
 #include "ffmpeg.h"
 #include "ffmpeg_filter.h"
+#include "graph/graphprint.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -2983,6 +2984,10 @@ read_frames:
     }
 
 finish:
+
+    if (print_graphs || print_graphs_file)
+        print_filtergraph(fg, fgt.graph);
+
     // EOF is normal termination
     if (ret == AVERROR_EOF)
         ret = 0;
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 6ec325f51e..3d1efe32f9 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -47,6 +47,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/stereo3d.h"
+#include "graph/graphprint.h"
 
 HWDevice *filter_hw_device;
 
@@ -75,6 +76,9 @@ float max_error_rate  = 2.0/3;
 char *filter_nbthreads;
 int filter_complex_nbthreads = 0;
 int vstats_version = 2;
+int print_graphs = 0;
+char *print_graphs_file = NULL;
+char *print_graphs_format = NULL;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1735,6 +1739,15 @@ const OptionDef options[] = {
         { .func_arg = opt_filter_complex_script },
         "deprecated, use -/filter_complex instead", "filename" },
 #endif
+    { "print_graphs",   OPT_TYPE_BOOL, 0,
+        { &print_graphs },
+        "print execution graph data to stderr" },
+    { "print_graphs_file", OPT_TYPE_STRING, 0,
+        { &print_graphs_file },
+        "write execution graph data to the specified file", "filename" },
+    { "print_graphs_format", OPT_TYPE_STRING, 0,
+        { &print_graphs_format },
+      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
new file mode 100644
index 0000000000..05c06f80fb
--- /dev/null
+++ b/fftools/graph/graphprint.c
@@ -0,0 +1,1101 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include <string.h>
+#include <stdatomic.h>
+
+#include "graphprint.h"
+
+#include <libavformat/url.h>
+
+#include "fftools/ffmpeg_filter.h"
+#include "fftools/ffmpeg_mux.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+#include "fftools/textformat/avtextformat.h"
+#include "fftools/textformat/tf_mermaid.h"
+#include "fftools/resources/resman.h"
+
+typedef enum {
+    SECTION_ID_ROOT,
+    SECTION_ID_FILTERGRAPHS,
+    SECTION_ID_FILTERGRAPH,
+    SECTION_ID_GRAPH_INPUTS,
+    SECTION_ID_GRAPH_INPUT,
+    SECTION_ID_GRAPH_OUTPUTS,
+    SECTION_ID_GRAPH_OUTPUT,
+    SECTION_ID_FILTERS,
+    SECTION_ID_FILTER,
+    SECTION_ID_FILTER_INPUTS,
+    SECTION_ID_FILTER_INPUT,
+    SECTION_ID_FILTER_OUTPUTS,
+    SECTION_ID_FILTER_OUTPUT,
+    SECTION_ID_HWFRAMESCONTEXT,
+    SECTION_ID_INPUTFILES,
+    SECTION_ID_INPUTFILE,
+    SECTION_ID_INPUTSTREAMS,
+    SECTION_ID_INPUTSTREAM,
+    SECTION_ID_OUTPUTFILES,
+    SECTION_ID_OUTPUTFILE,
+    SECTION_ID_OUTPUTSTREAMS,
+    SECTION_ID_OUTPUTSTREAM,
+    SECTION_ID_STREAMLINKS,
+    SECTION_ID_STREAMLINK,
+    SECTION_ID_DECODERS,
+    SECTION_ID_DECODER,
+    SECTION_ID_ENCODERS,
+    SECTION_ID_ENCODER,
+} SectionID;
+
+static struct AVTextFormatSection sections[] = {
+    [SECTION_ID_ROOT]            = { SECTION_ID_ROOT, "root", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER, { SECTION_ID_FILTERGRAPHS, SECTION_ID_INPUTFILES, SECTION_ID_OUTPUTFILES, SECTION_ID_DECODERS, SECTION_ID_ENCODERS, SECTION_ID_STREAMLINKS, -1 } },
+
+    [SECTION_ID_FILTERGRAPHS]    = { SECTION_ID_FILTERGRAPHS, "graphs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
+    [SECTION_ID_FILTERGRAPH]     = { SECTION_ID_FILTERGRAPH, "graph", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { SECTION_ID_GRAPH_INPUTS, SECTION_ID_GRAPH_OUTPUTS, SECTION_ID_FILTERS, -1 }, .element_name = "graph_info" },
+
+    [SECTION_ID_GRAPH_INPUTS]    = { SECTION_ID_GRAPH_INPUTS, "graph_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_INPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_INPUT]     = { SECTION_ID_GRAPH_INPUT, "graph_input", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_GRAPH_OUTPUTS]   = { SECTION_ID_GRAPH_OUTPUTS, "graph_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_OUTPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_OUTPUT]    = { SECTION_ID_GRAPH_OUTPUT, "graph_output", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTERS]         = { SECTION_ID_FILTERS, "filters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_FILTER, -1 }, .id_key = "graph_id" },
+    [SECTION_ID_FILTER]          = { SECTION_ID_FILTER, "filter", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { SECTION_ID_FILTER_INPUTS, SECTION_ID_FILTER_OUTPUTS, -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_INPUTS]   = { SECTION_ID_FILTER_INPUTS, "filter_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_INPUT, -1 } },
+    [SECTION_ID_FILTER_INPUT]    = { SECTION_ID_FILTER_INPUT, "filter_input", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "source_filter_id", .dest_id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_OUTPUTS]  = { SECTION_ID_FILTER_OUTPUTS, "filter_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_OUTPUT, -1 } },
+    [SECTION_ID_FILTER_OUTPUT]   = { SECTION_ID_FILTER_OUTPUT, "filter_output", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "filter_id", .dest_id_key = "dest_filter_id" },
+
+    [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context",  0, { -1 }, },
+
+    [SECTION_ID_INPUTFILES]      = { SECTION_ID_INPUTFILES, "inputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTFILE]       = { SECTION_ID_INPUTFILE, "inputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_INPUTSTREAMS]    = { SECTION_ID_INPUTSTREAMS, "inputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTSTREAM]     = { SECTION_ID_INPUTSTREAM, "inputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTFILES]     = { SECTION_ID_OUTPUTFILES, "outputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTFILE]      = { SECTION_ID_OUTPUTFILE, "outputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTSTREAMS]   = { SECTION_ID_OUTPUTSTREAMS, "outputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTSTREAM]    = { SECTION_ID_OUTPUTSTREAM, "outputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id", },
+
+    [SECTION_ID_STREAMLINKS]     = { SECTION_ID_STREAMLINKS, "streamlinks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAMLINK, -1 } },
+    [SECTION_ID_STREAMLINK]      = { SECTION_ID_STREAMLINK, "streamlink", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .src_id_key = "source_stream_id", .dest_id_key = "dest_stream_id" },
+
+    [SECTION_ID_DECODERS]        = { SECTION_ID_DECODERS, "decoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_DECODER, -1 } },
+    [SECTION_ID_DECODER]         = { SECTION_ID_DECODER, "decoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "source_id", .dest_id_key = "id" },
+
+    [SECTION_ID_ENCODERS]        = { SECTION_ID_ENCODERS, "encoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_ENCODER, -1 } },
+    [SECTION_ID_ENCODER]         = { SECTION_ID_ENCODER, "encoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "id", .dest_id_key = "dest_id" },
+};
+
+typedef struct GraphPrintContext {
+    AVTextFormatContext *tfc;
+    AVTextWriterContext *wctx;
+    AVDiagramConfig diagram_config;
+
+    int id_prefix_num;
+    int is_diagram;
+    int opt_flags;
+    int skip_buffer_filters;
+    AVBPrint pbuf;
+
+} GraphPrintContext;
+
+/* Text Format API Shortcuts */
+#define print_id(k, v)          print_sanizied_id(gpc, k, v, 0)
+#define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
+#define print_int(k, v)         avtext_print_integer(tfc, k, v)
+#define print_int_opt(k, v)     avtext_print_integer_flags(tfc, k, v, gpc->opt_flags)
+#define print_q(k, v, s)        avtext_print_rational(tfc, k, v, s)
+#define print_str(k, v)         avtext_print_string(tfc, k, v, 0)
+#define print_str_opt(k, v)     avtext_print_string(tfc, k, v, gpc->opt_flags)
+#define print_val(k, v, u)      avtext_print_unit_int(tfc, k, v, u)
+
+#define print_fmt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, 0);    \
+} while (0)
+
+#define print_fmt_opt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, gpc->opt_flags);    \
+} while (0)
+
+
+static atomic_int prefix_num = 0;
+
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    unsigned i;
+    for (i = 0; src[i] && i < dst_size - 1; i++)
+        dst[i]      = (char)av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static char *get_extension(const char *url)
+{
+    const char *ext;
+    URLComponents uc;
+    int ret;
+    char scratchpad[128];
+
+    if (!url)
+        return 0;
+
+    ret = ff_url_decompose(&uc, url, NULL);
+    if (ret < 0)
+        return NULL;
+    for (ext = uc.query; *ext != '.' && ext > uc.path; ext--) {
+    }
+
+    if (*ext != '.')
+        return 0;
+    if (uc.query - ext > sizeof(scratchpad))
+        return NULL; //not enough memory in our scratchpad
+    av_strlcpy(scratchpad, ext + 1, uc.query - ext);
+
+    return av_strdup(scratchpad);
+}
+
+static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+
+    if (!hw_device_context)
+        return;
+
+    print_int_opt("has_hw_device_context", 1);
+    print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
+}
+
+static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    const AVPixFmtDescriptor *pix_desc_hw;
+    const AVPixFmtDescriptor *pix_desc_sw;
+
+    if (!hw_frames_context || !hw_frames_context->device_ctx)
+        return;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT);
+
+    print_int_opt("has_hw_frames_context", 1);
+    print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
+
+    pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
+    if (pix_desc_hw) {
+        print_str("hw_pixel_format", pix_desc_hw->name);
+        if (pix_desc_hw->alias)
+            print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
+    }
+
+    pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
+    if (pix_desc_sw) {
+        print_str("sw_pixel_format", pix_desc_sw->name);
+        if (pix_desc_sw->alias)
+            print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
+    }
+
+    print_int_opt("width", hw_frames_context->width);
+    print_int_opt("height", hw_frames_context->height);
+    print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBufferRef *hw_frames_ctx;
+    char layout_string[64];
+
+    if (!link)
+        return;
+
+    hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
+
+    print_str_opt("media_type", av_get_media_type_string(link->type));
+
+    switch (link->type) {
+    case AVMEDIA_TYPE_VIDEO:
+
+        if (hw_frames_ctx && hw_frames_ctx->data) {
+            AVHWFramesContext *      hwfctx      = (AVHWFramesContext *)hw_frames_ctx->data;
+            const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
+            const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
+            if (pix_desc_hw && pix_desc_sw)
+                print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
+        } else {
+            print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
+        }
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        print_q("sar", link->sample_aspect_ratio, ':');
+
+        if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
+            print_str_opt("color_range", av_color_range_name(link->color_range));
+
+        if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
+            print_str("color_space", av_color_space_name(link->colorspace));
+        break;
+
+    case AVMEDIA_TYPE_SUBTITLE:
+        ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        break;
+
+    case AVMEDIA_TYPE_AUDIO:
+        av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
+        print_str("channel_layout", layout_string);
+        print_val("channels", link->ch_layout.nb_channels, "ch");
+        if (tfc->show_value_unit)
+            print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
+        else
+            print_val("sample_rate", link->sample_rate, "Hz");
+
+        break;
+    }
+
+    print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
+
+    if (hw_frames_ctx && hw_frames_ctx->data)
+        print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
+}
+
+static char sanitize_char(const char c)
+{
+    if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+        return c;
+    return '_';
+}
+
+static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBPrint buf;
+
+    if (!key || !id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    print_str(key, buf.str);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+    AVBPrint buf;
+
+    if (!id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    sec_ctx.context_id = buf.str;
+
+    avtext_print_section_header(tfc, &sec_ctx, section_id);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static const char *get_filterpad_name(const AVFilterPad *pad)
+{
+    return pad ? avfilter_pad_get_name(pad, 0) : "pad";
+}
+
+static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
+
+    ////print_id("filter_id", filter->name);
+
+    if (filter->filter) {
+        print_str("filter_name", filter->filter->name);
+        print_str_opt("description", filter->filter->description);
+        print_int_opt("nb_inputs", filter->nb_inputs);
+        print_int_opt("nb_outputs", filter->nb_outputs);
+    }
+
+    if (filter->hw_device_ctx) {
+        AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
+        print_hwdevicecontext(gpc, device_context);
+        if (filter->extra_hw_frames > 0)
+            print_int("extra_hw_frames", filter->extra_hw_frames);
+    }
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
+
+    for (unsigned i = 0; i < filter->nb_inputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->inputs[i];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
+        sec_ctx.context_type = NULL;
+
+        print_int_opt("input_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
+
+        dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
+        if (dic_entry) {
+            char buf[256];
+            (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
+            print_id_noprefix("source_filter_id", buf);
+        } else {
+            print_id("source_filter_id", link->src->name);
+        }
+
+        print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
+
+    for (unsigned i = 0; i < filter->nb_outputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->outputs[i];
+        char buf[256];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
+        sec_ctx.context_type = NULL;
+
+        dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
+        if (dic_entry) {
+            (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
+            print_id_noprefix("dest_filter_id", buf);
+        } else {
+            print_id("dest_filter_id", link->dst->name);
+        }
+
+        print_int_opt("output_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->srcpad));
+        ////print_id("dest_filter_id", link->dst->name);
+        print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER
+}
+
+static void init_sections(void)
+{
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
+        sections[i].show_all_entries = 1;
+}
+
+static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVDictionary *input_map = NULL;
+    AVDictionary *output_map = NULL;
+
+    print_int("graph_index", fg->index);
+    print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+    print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
+    print_str("description", fgp->graph_desc);
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
+
+    for (int i = 0; i < fg->nb_inputs; i++) {
+        InputFilterPriv *ifilter = ifp_from_ifilter(fg->inputs[i]);
+        enum AVMediaType media_type = ifilter->type;
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
+
+        print_int("input_index", ifilter->index);
+
+        if (ifilter->linklabel)
+            print_str("link_label", (const char*)ifilter->linklabel);
+
+        if (ifilter->filter) {
+            print_id("filter_id", ifilter->filter->name);
+            print_str("filter_name", ifilter->filter->filter->name);
+        }
+
+        if (ifilter->linklabel && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
+        else if (ifilter->opts.name && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->opts.name, 0);
+
+        print_str("media_type", av_get_media_type_string(media_type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
+
+    for (int i = 0; i < fg->nb_outputs; i++) {
+        OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
+
+        print_int("output_index", ofilter->index);
+
+        print_str("name", ofilter->name);
+
+        if (fg->outputs[i]->linklabel)
+            print_str("link_label", (const char*)fg->outputs[i]->linklabel);
+
+        if (ofilter->filter) {
+            print_id("filter_id", ofilter->filter->name);
+            print_str("filter_name", ofilter->filter->filter->name);
+        }
+
+        if (ofilter->name && ofilter->filter)
+            av_dict_set(&output_map, ofilter->filter->name, ofilter->name, 0);
+
+
+        print_str("media_type", av_get_media_type_string(fg->outputs[i]->type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
+
+    if (graph) {
+        AVTextFormatSectionContext sec_ctx = { 0 };
+
+        sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTERS);
+
+        if (gpc->is_diagram) {
+            print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+            print_str("description", fgp->graph_desc);
+            print_str("id", sec_ctx.context_id);
+        }
+
+        av_freep(&sec_ctx.context_id);
+
+        for (unsigned i = 0; i < graph->nb_filters; i++) {
+            AVFilterContext *filter = graph->filters[i];
+
+            if (gpc->skip_buffer_filters) {
+                if (av_dict_get(input_map, filter->name, NULL, 0))
+                    continue;
+                if (av_dict_get(output_map, filter->name, NULL, 0))
+                    continue;
+            }
+
+            sec_ctx.context_id = filter->name;
+
+            print_filter(gpc, filter, input_map, output_map);
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
+    }
+
+    // Clean up dictionaries
+    av_dict_free(&input_map);
+    av_dict_free(&output_map);
+}
+
+static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    AVTextFormatContext       *tfc = gpc->tfc;
+    AVBPrint                   buf;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+    print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
+
+    for (int n = nb_ifiles - 1; n >= 0; n--) {
+        InputFile *ifi = ifiles[n];
+        AVFormatContext *fc = ifi->ctx;
+
+        sec_ctx.context_id = av_asprintf("Input_%d", n);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
+        av_freep(&sec_ctx.context_id);
+
+        print_fmt("index", "%d", ifi->index);
+
+        if (fc) {
+            print_str("demuxer_name", fc->iformat->name);
+            if (fc->url) {
+                char *extension = get_extension(fc->url);
+                if (extension) {
+                    print_str("file_extension", extension);
+                    av_freep(&extension);
+                }
+                print_str("url", fc->url);
+            }
+        }
+
+        sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
+
+        av_freep(&sec_ctx.context_id);
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+            const AVCodecDescriptor *codec_desc;
+
+            if (!ist || !ist->par)
+                continue;
+
+            codec_desc = avcodec_descriptor_get(ist->par->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
+
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_in_%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else if (ist->dec) {
+                char char_buf[256];
+                av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
+                av_bprintf(&buf, "%s", "Attachment");
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
+                av_bprintf(&buf, "%s", "Data");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ist->index);
+
+            if (ist->dec)
+                print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
+
+
+    print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
+
+    for (int n = 0; n < nb_ifiles; n++) {
+        InputFile *ifi = ifiles[n];
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+
+            if (!ist->decoder)
+                continue;
+
+            sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_DECODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("source_id", "r_in_%d_%d", n, i);
+            print_fmt("id", "in_%d_%d", n, i);
+
+            ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            print_fmt("name", "%s", ist->dec->name);
+
+            print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_DECODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            ////const AVCodecDescriptor *codec_desc;
+
+            if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
+                continue;
+
+            ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_ENCODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "out__%d_%d", n, i);
+            print_fmt("dest_id", "r_out__%d_%d", n, i);
+
+            print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
+
+    for (int n = nb_ofiles - 1; n >= 0; n--) {
+        OutputFile *of = ofiles[n];
+        Muxer *muxer = (Muxer *)of;
+
+        if (!muxer->fc)
+            continue;
+
+        sec_ctx.context_id = av_asprintf("Output_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
+
+        av_freep(&sec_ctx.context_id);
+
+        ////print_str_opt("index", av_get_media_type_string(of->index));
+        print_fmt("index", "%d", of->index);
+        ////print_str("url", of->url);
+        print_str("muxer_name", muxer->fc->oformat->name);
+        if (of->url) {
+            char *extension = get_extension(of->url);
+            if (extension) {
+                print_str("file_extension", extension);
+                av_freep(&extension);
+            }
+            print_str("url", of->url);
+        }
+
+        sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_out__%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else {
+                av_bprintf(&buf, "%s", "unknown");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ost->index);
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
+
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->ist && !ost->filter) {
+                sec_ctx.context_type = av_get_media_type_string(ost->type);
+                avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_STREAMLINK);
+                sec_ctx.context_type = NULL;
+
+                if (ost->enc) {
+                    print_fmt("dest_stream_id", "out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Transcode");
+                } else {
+                    print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Stream Copy");
+                }
+
+                print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+                avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
+
+    return 0;
+}
+
+
+static void uninit_graphprint(GraphPrintContext *gpc)
+{
+    if (gpc->tfc)
+        avtext_context_close(&gpc->tfc);
+
+    if (gpc->wctx)
+        avtextwriter_context_close(&gpc->wctx);
+
+    // Finalize the print buffer if it was initialized
+    av_bprint_finalize(&gpc->pbuf, NULL);
+}
+
+static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
+{
+    const AVTextFormatter *text_formatter;
+    AVTextFormatContext *tfc = NULL;
+    AVTextWriterContext *wctx = NULL;
+    GraphPrintContext *gpc = NULL;
+    char *w_args = NULL;
+    char *w_name;
+    int ret;
+
+    init_sections();
+    *pgpc = NULL;
+
+    av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("json");
+    if (!print_graphs_format) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &w_args);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    text_formatter = avtext_get_formatter_by_name(w_name);
+    if (!text_formatter) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtextwriter_create_buffer(&wctx, target_buf);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    AVTextFormatOptions tf_options = { .show_optional_fields = -1 };
+    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), tf_options, NULL);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    gpc = av_mallocz(sizeof(GraphPrintContext));
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    gpc->wctx = wctx;
+    gpc->tfc = tfc;
+    av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
+    gpc->is_diagram = !!(tfc->formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
+    if (gpc->is_diagram) {
+        tfc->show_value_unit = 1;
+        tfc->show_optional_fields = -1;
+        gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+        gpc->skip_buffer_filters = 1;
+        ////} else {
+        ////    gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+    }
+
+    if (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml")) {
+        gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
+
+        if (!strcmp(text_formatter->name, "mermaidhtml"))
+            gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
+
+        av_diagram_init(tfc, &gpc->diagram_config);
+    }
+
+    *pgpc = gpc;
+
+    return 0;
+
+fail:
+    if (tfc)
+        avtext_context_close(&tfc);
+    if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
+        avtextwriter_context_close(&wctx);
+    av_freep(&gpc);
+
+    return ret;
+}
+
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVBPrint *target_buf = &fgp->graph_print_buf;
+    int ret;
+
+    if (!fg || !fgp) {
+        av_log(NULL, AV_LOG_ERROR, "Invalid filter graph provided\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (target_buf->len)
+        av_bprint_finalize(target_buf, NULL);
+
+    ret = init_graphprint(&gpc, target_buf);
+    if (ret)
+        return ret;
+
+    if (!gpc) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to initialize graph print context\n");
+        return AVERROR(ENOMEM);
+    }
+
+    tfc = gpc->tfc;
+
+    // Due to the threading model each graph needs to print itself into a buffer
+    // from its own thread. The actual printing happens short before cleanup in ffmpeg.c
+    // where all graphs are assembled together. To make this work, we need to put the
+    // formatting context into the same state like it would be when printing all at once,
+    // so here we print the section headers and clear the buffer to get into the right state.
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+
+    av_bprint_clear(target_buf);
+
+    print_filtergraph_single(gpc, fg, graph);
+
+    if (gpc->is_diagram) {
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+    }
+
+    uninit_graphprint(gpc);
+
+    return 0;
+}
+
+static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    AVBPrint target_buf;
+    int ret;
+
+    ret = init_graphprint(&gpc, &target_buf);
+    if (ret)
+        goto cleanup;
+
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto cleanup;
+    }
+
+    tfc = gpc->tfc;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+
+    for (int i = 0; i < nb_graphs; i++) {
+        FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
+        AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+        if (graph_buf->len > 0) {
+            avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+            av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+            av_bprint_finalize(graph_buf, NULL);
+            avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        }
+    }
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->fg_simple) {
+                FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
+                AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+                if (graph_buf->len > 0) {
+                    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+                    av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+                    av_bprint_finalize(graph_buf, NULL);
+                    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+                }
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+
+    print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ROOT
+
+    if (print_graphs_file) {
+        AVIOContext *avio = NULL;
+
+        if (!strcmp(print_graphs_file, "-")) {
+            printf("%s", target_buf.str);
+        } else {
+            ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
+                goto cleanup;
+            }
+
+            avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
+            avio_flush(avio);
+
+            if ((ret = avio_closep(&avio)) < 0)
+                av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
+        }
+    }
+
+    if (print_graphs)
+        av_log(NULL, AV_LOG_INFO, "%s    %c", target_buf.str, '\n');
+
+cleanup:
+    // Properly clean up resources
+    if (gpc)
+        uninit_graphprint(gpc);
+
+    // Ensure the target buffer is properly finalized
+    av_bprint_finalize(&target_buf, NULL);
+
+    return ret;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+}
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
new file mode 100644
index 0000000000..9f043cc273
--- /dev/null
+++ b/fftools/graph/graphprint.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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_GRAPH_GRAPHPRINT_H
+#define FFTOOLS_GRAPH_GRAPHPRINT_H
+
+#include "fftools/ffmpeg.h"
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles);
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 86220dc676..f41c395f07 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -697,6 +697,8 @@ static void formatters_register_all(void)
     registered_formatters[4] = &avtextformatter_ini;
     registered_formatters[5] = &avtextformatter_json;
     registered_formatters[6] = &avtextformatter_xml;
+    registered_formatters[7] = &avtextformatter_mermaid;
+    registered_formatters[8] = &avtextformatter_mermaidhtml;
 }
 
 const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index f4175c6f48..43450148fe 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -31,6 +31,12 @@
 
 #define SECTION_MAX_NB_CHILDREN 11
 
+typedef struct AVTextFormatSectionContext {
+    char *context_id;
+    const char *context_type;
+    int context_flags;
+} AVTextFormatSectionContext;
+
 
 typedef struct AVTextFormatSection {
     int id;             ///< unique id identifying a section
@@ -42,6 +48,10 @@ typedef struct AVTextFormatSection {
                                            ///  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
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE       32 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS      64 ///< ...
+#define AV_TEXTFORMAT_SECTION_PRINT_TAGS         128 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH   256 ///< ...
 
     int flags;
     const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
@@ -50,12 +60,17 @@ typedef struct AVTextFormatSection {
     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;
+    const char *id_key;          ///< name of the key to be used as the id 
+    const char *src_id_key;     ///< name of the key to be used as the source id for diagram connections
+    const char *dest_id_key;   ///< name of the key to be used as the target id for diagram connections
+    const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
 } AVTextFormatSection;
 
 typedef struct AVTextFormatContext AVTextFormatContext;
 
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
+#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER         4
 
 typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
@@ -64,6 +79,18 @@ typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_NB
 } StringValidation;
 
+typedef enum {
+    AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_NONDIR,
+    AV_TEXTFORMAT_LINKTYPE_HIDDEN,
+    AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
+} AVTextFormatLinkType;
+
 typedef struct AVTextFormatter {
     const AVClass *priv_class;      ///< private class of the formatter, if any
     int priv_size;                  ///< private size for the formatter context
@@ -169,5 +196,7 @@ extern const AVTextFormatter avtextformatter_flat;
 extern const AVTextFormatter avtextformatter_ini;
 extern const AVTextFormatter avtextformatter_json;
 extern const AVTextFormatter avtextformatter_xml;
+extern const AVTextFormatter avtextformatter_mermaid;
+extern const AVTextFormatter avtextformatter_mermaidhtml;
 
 #endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
diff --git a/fftools/textformat/tf_mermaid.c b/fftools/textformat/tf_mermaid.c
new file mode 100644
index 0000000000..6147cf6eea
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.c
@@ -0,0 +1,658 @@
+/*
+ * 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 "tf_internal.h"
+#include "tf_mermaid.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+
+static const char *init_directive = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 10,"
+        "\"nodeSpacing\": 10,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"flowchart\": { "
+            "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
+            "\"diagramPadding\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char* init_directive_er = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"layout\": \"elk\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 65,"
+        "\"nodeSpacing\": 60,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"er\": { "
+            "\"diagramPadding\": 12, "
+            "\"entityPadding\": 4, "
+            "\"minEntityWidth\": 150, "
+            "\"minEntityHeight\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char *theme_css_er = ""
+
+    // Variables
+            ".root { "
+                "--ff-colvideo: #6eaa7b; "
+                "--ff-colaudio: #477fb3; "
+                "--ff-colsubtitle: #ad76ab; "
+                "--ff-coltext: #666; "
+            "} "
+            " g.nodes g.node.default rect.basic.label-container, "
+            " g.nodes g.node.default path { "
+            "     rx: 1; "
+            "     ry: 1; "
+            "     stroke-width: 1px !important; "
+            "     stroke: #e9e9e9 !important; "
+            "     fill: url(#ff-filtergradient) !important; "
+            "     filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
+            "     fill: white !important; "
+            " } "
+            "  "
+            " .relationshipLine { "
+            "     stroke: gray; "
+            "     stroke-width: 1; "
+            "     fill: none; "
+            "     filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
+            " } "
+            "  "
+            " g.node.default g.label.name  foreignObject > div > span > p, "
+            " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
+            "     font-size: 0.95rem; "
+            "     font-weight: 500; "
+            "     text-transform: uppercase; "
+            "     min-width: 5.5rem; "
+            "     margin-bottom: 0.5rem; "
+            "      "
+            " } "
+            "  "
+            " .edgePaths path { "
+            "     marker-end: none; "
+            "     marker-start: none; "
+            "  "
+            "} ";
+
+
+/* Mermaid Graph output */
+
+typedef struct MermaidContext {
+    const AVClass *class;
+    AVDiagramConfig *diagram_config;
+    int subgraph_count;
+    int within_tag;
+    int indent_level;
+    int create_html;
+
+    // Options
+    int enable_link_colors; // Requires Mermaid 11.5
+
+    struct section_data {
+        const char *section_id;
+        const char *section_type;
+        const char *src_id;
+        const char *dest_id;
+        AVTextFormatLinkType link_type;
+        int current_is_textblock;
+        int current_is_stadium;
+        int subgraph_start_incomplete;
+    }  section_data[SECTION_MAX_NB_LEVELS];
+
+    unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint link_buf; ///< print buffer for writing diagram links
+    AVDictionary *link_dict;
+} MermaidContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(MermaidContext, x)
+
+static const AVOption mermaid_options[] = {
+    { "link_coloring",    "enable colored links (requires Mermaid >= 11.5)",  OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL,   { .i64 = 1 },  0, 1 },
+    ////{"diagram_css",      "CSS for the diagram",                              OFFSET(diagram_css),        AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    ////{"html_template",    "Template HTML",                                    OFFSET(html_template),      AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    { NULL },
+};
+
+DEFINE_FORMATTER_CLASS(mermaid);
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
+{
+    MermaidContext *mmc = tfc->priv;
+    mmc->diagram_config = diagram_config;
+}
+
+static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
+{
+    MermaidContext *mmc = tfc->priv;
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&buf, "%s--%s", src, dest);
+
+    if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
+        return 1;
+
+    av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
+
+    return 0;
+}
+
+static av_cold int mermaid_init(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
+    return 0;
+}
+
+static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    int ret = mermaid_init(tfc);
+
+    if (ret < 0)
+        return ret;
+
+    mmc->create_html = 1;
+
+    return 0;
+}
+
+#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
+
+static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
+{
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSectionContext *sec_ctx = data;
+
+    if (tfc->level == 0) {
+        char *directive;
+        AVBPrint css_buf;
+        const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
+        char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
+        (void)theme_css_er;
+        ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
+        av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+        av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
+        av_freep(&single_line_css);
+
+        directive = av_strireplace(diag_directive, "__###__", css_buf.str);
+        if (mmc->create_html) {
+            uint64_t length;
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+
+            length = token_pos - mmc->diagram_config->html_template;
+            for (uint64_t i = 0; i < length; i++)
+                writer_w8(tfc, mmc->diagram_config->html_template[i]);
+        }
+
+        writer_put_str(tfc, directive);
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+            writer_put_str(tfc, "flowchart LR\n");
+        ////writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
+            writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%);     \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%);   \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%);     \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            writer_put_str(tfc, "erDiagram\n");
+            break;
+        }
+
+        return;
+    }
+
+    if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
+        AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
+
+        if (parent_sec_data.subgraph_start_incomplete) {
+
+            if (parent_buf->len > 0)
+                writer_printf(tfc, "%s", parent_buf->str);
+
+            writer_put_str(tfc, "</div>\"]\n");
+
+            mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
+        }
+    }
+
+    av_freep(&mmc->section_data[tfc->level].section_id);
+    av_freep(&mmc->section_data[tfc->level].section_type);
+    av_freep(&mmc->section_data[tfc->level].src_id);
+    av_freep(&mmc->section_data[tfc->level].dest_id);
+    mmc->section_data[tfc->level].current_is_textblock = 0;
+    mmc->section_data[tfc->level].current_is_stadium = 0;
+    mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
+    mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
+
+    // NOTE: av_strdup() allocations aren't checked
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+            MM_INDENT();
+            writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+
+            mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+                if (sec_ctx->context_flags & 1) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_textblock = 1;
+                } else if (sec_ctx->context_flags & 2) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s([\"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_stadium = 1;
+                } else {
+                    MM_INDENT();
+                    writer_printf(tfc, "%s(\"", sec_ctx->context_id);
+                }
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+                MM_INDENT();
+                writer_printf(tfc, "%s {\n", sec_ctx->context_id);
+                break;
+            }
+
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
+
+        if (sec_ctx && sec_ctx->context_type)
+            writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
+        else
+            writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        av_bprint_clear(buf);
+        mmc->nb_link_captions[tfc->level] = 0;
+
+        if (sec_ctx && sec_ctx->context_type)
+            mmc->section_data[tfc->level].section_type = av_strdup(sec_ctx->context_type);
+
+        ////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(tfc, " type=\"%s\"", buf.str);
+    }
+}
+
+static void mermaid_print_section_footer(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
+        writer_put_str(tfc, "</div>");
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (sec_data.current_is_textblock) {
+                writer_printf(tfc, "\"}\n", section->name);
+
+                if (sec_data.section_id) {
+                    MM_INDENT();
+                    writer_put_str(tfc, "class ");
+                    writer_put_str(tfc, sec_data.section_id);
+                    writer_put_str(tfc, " ff-");
+                    writer_put_str(tfc, section->name);
+                    writer_put_str(tfc, "\n");
+                }
+            } else if (sec_data.current_is_stadium) {
+                writer_printf(tfc, "\"]):::ff-%s\n", section->name);
+            } else {
+                writer_printf(tfc, "\"):::ff-%s\n", section->name);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            MM_INDENT();
+            writer_put_str(tfc, "}\n\n");
+            break;
+        }
+
+        mmc->indent_level--;
+
+    } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
+
+        MM_INDENT();
+        writer_put_str(tfc, "end\n");
+
+        if (sec_data.section_id) {
+            MM_INDENT();
+            writer_put_str(tfc, "class ");
+            writer_put_str(tfc, sec_data.section_id);
+            writer_put_str(tfc, " ff-");
+            writer_put_str(tfc, section->name);
+            writer_put_str(tfc, "\n");
+        }
+
+        mmc->indent_level--;
+    }
+
+    if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
+        if (sec_data.src_id && sec_data.dest_id
+            && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+
+                if (sec_data.section_type && mmc->enable_link_colors)
+                    av_bprintf(&mmc->link_buf, "\n  %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
+                else
+                    av_bprintf(&mmc->link_buf, "\n  %s ==", sec_data.src_id);
+
+                if (buf->len > 0) {
+                    av_bprintf(&mmc->link_buf, " \"%s", buf->str);
+
+                    for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
+                        av_bprintf(&mmc->link_buf, "<br>&nbsp;");
+
+                    av_bprintf(&mmc->link_buf, "\" ==");
+                }
+
+                av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+
+                av_bprintf(&mmc->link_buf, "\n  %s", sec_data.src_id);
+
+                switch (sec_data.link_type) {
+                case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
+                    break;
+                default:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                }
+
+                av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
+
+                break;
+            }
+
+    if (tfc->level == 0) {
+
+        writer_put_str(tfc, "\n");
+        if (mmc->create_html) {
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+            token_pos += strlen("__###__");
+            writer_put_str(tfc, token_pos);
+        }
+    }
+
+    if (tfc->level == 1) {
+
+        if (mmc->link_buf.len > 0) {
+            writer_put_str(tfc, mmc->link_buf.str);
+            av_bprint_clear(&mmc->link_buf);
+        }
+
+        writer_put_str(tfc, "\n");
+    }
+}
+
+static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
+                                const char *str, int64_t num, const int is_int)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+    int exit = 0;
+
+    if (section->id_key && !strcmp(section->id_key, key)) {
+        mmc->section_data[tfc->level].section_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
+        mmc->section_data[tfc->level].dest_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->src_id_key && !strcmp(section->src_id_key, key)) {
+        mmc->section_data[tfc->level].src_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->linktype_key && !strcmp(section->linktype_key, key)) {
+        mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
+        exit = 1;
+    }
+
+    //if (exit)
+    //    return;
+
+    if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
+        || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
+
+        if (exit)
+            return;
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (is_int) {
+                writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
+            } else {
+                ////AVBPrint b;
+                ////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
+                const char *tmp = av_strireplace(str, "\"", "'");
+                ////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
+                writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
+                av_freep(&tmp);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+            if (!is_int && str)
+            {
+                const char *col_type;
+
+                if (key[0] == '_')
+                    return;
+
+                if (sec_data.section_id && !strcmp(str, sec_data.section_id))
+                    col_type = "PK";
+                else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
+                    col_type = "FK";
+                else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
+                    col_type = "FK";
+                else
+                    col_type = "";
+
+                MM_INDENT();
+
+                if (is_int)
+                    writer_printf(tfc, "    %s %"PRId64" %s\n", key, num, col_type);
+                else
+                    writer_printf(tfc, "    %s %s %s\n", key, str, col_type);
+            }
+            break;
+        }
+
+    } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        if (exit)
+            return;
+
+        if (buf->len > 0)
+            av_bprintf(buf, "%s", "<br>");
+
+        av_bprintf(buf, "");
+        if (is_int)
+            av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
+        else
+            av_bprintf(buf, "<span>%s</span>", str);
+
+        mmc->nb_link_captions[tfc->level]++;
+    }
+}
+
+static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
+{
+    mermaid_print_value(tfc, key, value, 0, 0);
+}
+
+static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
+{
+    mermaid_print_value(tfc, key, NULL, value, 1);
+}
+
+const AVTextFormatter avtextformatter_mermaid = {
+    .name                 = "mermaid",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
+
+
+const AVTextFormatter avtextformatter_mermaidhtml = {
+    .name                 = "mermaidhtml",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init_html,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
diff --git a/fftools/textformat/tf_mermaid.h b/fftools/textformat/tf_mermaid.h
new file mode 100644
index 0000000000..aff73bf9f3
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.h
@@ -0,0 +1,41 @@
+/*
+ * 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_TF_MERMAID_H
+#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
+
+typedef enum {
+    AV_DIAGRAMTYPE_GRAPH,
+    AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
+} AVDiagramType;
+
+typedef struct AVDiagramConfig {
+    AVDiagramType diagram_type;
+    const char *diagram_css;
+    const char *html_template;
+} AVDiagramConfig;
+
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
+
+void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
+
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */
\ No newline at end of file
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v6 13/13] fftools/graphprint: Now, make it a Killer-Feature!
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (11 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 12/13] fftools/graphprint: Add execution graph printing softworkz
@ 2025-04-24  1:13           ` softworkz
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
  13 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-24  1:13 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

remember this: -sg   <= show-graph

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi              |   4 +
 fftools/Makefile             |   1 +
 fftools/ffmpeg.c             |   2 +-
 fftools/ffmpeg.h             |   1 +
 fftools/ffmpeg_filter.c      |   2 +-
 fftools/ffmpeg_opt.c         |   4 +
 fftools/graph/filelauncher.c | 205 +++++++++++++++++++++++++++++++++++
 fftools/graph/graphprint.c   |  48 +++++++-
 fftools/graph/graphprint.h   |  32 ++++++
 9 files changed, 296 insertions(+), 3 deletions(-)
 create mode 100644 fftools/graph/filelauncher.c

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 35675b5309..6e9e7aed0e 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1404,6 +1404,10 @@ Writes execution graph details to the specified file in the format set via -prin
 Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
 The default format is json.
 
+@item -sg (@emph{global})
+Writes the execution graph to a temporary html file (mermaidhtml format) and 
+tries to launch it in the default browser.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index 361a4fd574..56a2910212 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -22,6 +22,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
     fftools/graph/graphprint.o        \
+    fftools/graph/filelauncher.o      \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
     fftools/textformat/avtextformat.o \
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index 6766ec209c..9875a1f7fd 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -309,7 +309,7 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
 
     if (do_benchmark) {
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7fbf0ad532..49fea0307d 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -721,6 +721,7 @@ extern int print_graphs;
 extern char *print_graphs_file;
 extern char *print_graphs_format;
 extern int auto_conversion_filters;
+extern int show_graph;
 
 extern const AVIOInterruptCB int_cb;
 
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index b774606562..e82e333b7f 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -2985,7 +2985,7 @@ read_frames:
 
 finish:
 
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraph(fg, fgt.graph);
 
     // EOF is normal termination
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3d1efe32f9..24713d640f 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -79,6 +79,7 @@ int vstats_version = 2;
 int print_graphs = 0;
 char *print_graphs_file = NULL;
 char *print_graphs_format = NULL;
+int show_graph = 0;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1748,6 +1749,9 @@ const OptionDef options[] = {
     { "print_graphs_format", OPT_TYPE_STRING, 0,
         { &print_graphs_format },
       "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
+    { "sg",   OPT_TYPE_BOOL, 0,
+        { &show_graph },
+        "create execution graph as temporary html file and try to launch it in the default browser" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/filelauncher.c b/fftools/graph/filelauncher.c
new file mode 100644
index 0000000000..0cf5f15cf1
--- /dev/null
+++ b/fftools/graph/filelauncher.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(_WIN32)
+#  include <windows.h>
+#  include <shellapi.h>
+#else
+#  include <sys/time.h>
+#  include <time.h>
+#endif
+#include "graphprint.h"
+
+int ff_open_html_in_browser(const char *html_path)
+{
+    if (!html_path || !*html_path)
+        return -1;
+
+#if defined(_WIN32)
+
+    // --- Windows ---------------------------------
+    {
+        HINSTANCE rc = ShellExecuteA(NULL, "open", html_path, NULL, NULL, SW_SHOWNORMAL);
+        if ((UINT_PTR)rc <= 32) {
+            // Fallback: system("start ...")
+            char cmd[1024];
+            _snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "start \"\" \"%s\"", html_path);
+            if (system(cmd) != 0)
+                return -1;
+        }
+        return 0;
+    }
+
+#elif defined(__APPLE__)
+
+    // --- macOS -----------------------------------
+    {
+        // "open" is the macOS command to open a file/URL with the default application
+        char cmd[1024];
+        snprintf(cmd, sizeof(cmd), "open '%s' 1>/dev/null 2>&1 &", html_path);
+        if (system(cmd) != 0)
+            return -1;
+        return 0;
+    }
+
+#else
+
+    // --- Linux / Unix-like -----------------------
+    // We'll try xdg-open, then gnome-open, then kfmclient
+    {
+        // Helper macro to try one browser command
+        // Returns 0 on success, -1 on failure
+        #define TRY_CMD(prog) do {                                   \
+            char buf[1024];                                          \
+            snprintf(buf, sizeof(buf), "%s '%s' 1>/dev/null 2>&1 &", \
+                     (prog), html_path);                              \
+            int ret = system(buf);                                    \
+            /* On Unix: system() returns -1 if the shell can't run. */\
+            /* Otherwise, check exit code in lower 8 bits.           */\
+            if (ret != -1 && WIFEXITED(ret) && WEXITSTATUS(ret) == 0) \
+                return 0;                                             \
+        } while (0)
+
+        TRY_CMD("xdg-open");
+        TRY_CMD("gnome-open");
+        TRY_CMD("kfmclient exec");
+
+        fprintf(stderr, "Could not open '%s' in a browser.\n", html_path);
+        return -1;
+    }
+
+#endif
+}
+
+
+int ff_get_temp_dir(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    // --- Windows ------------------------------------
+    {
+        // GetTempPathA returns length of the string (including trailing backslash).
+        // If the return value is greater than buffer size, it's an error.
+        DWORD len = GetTempPathA((DWORD)size, buf);
+        if (len == 0 || len > size) {
+            // Could not retrieve or buffer is too small
+            return -1;
+        }
+        return 0;
+    }
+
+#else
+
+    // --- macOS / Linux / Unix -----------------------
+    // Follow typical POSIX convention: check common env variables
+    // and fallback to /tmp if not found.
+    {
+        const char *tmp = getenv("TMPDIR");
+        if (!tmp || !*tmp) tmp = getenv("TMP");
+        if (!tmp || !*tmp) tmp = getenv("TEMP");
+        if (!tmp || !*tmp) tmp = "/tmp";
+
+        // Copy into buf, ensure there's a trailing slash
+        size_t len = strlen(tmp);
+        if (len + 2 > size) {
+            // Need up to len + 1 for slash + 1 for null terminator
+            return -1;
+        }
+
+        strcpy(buf, tmp);
+        // Append slash if necessary
+        if (buf[len - 1] != '/' && buf[len - 1] != '\\') {
+#if defined(__APPLE__)
+            // On macOS/Unix, use forward slash
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#else
+            // Technically on Unix it's always '/', but here's how you'd do if needed:
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#endif
+        }
+        return 0;
+    }
+
+#endif
+}
+
+int ff_make_timestamped_html_name(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    /*----------- Windows version -----------*/
+    SYSTEMTIME st;
+    GetLocalTime(&st);
+    /*
+      st.wYear, st.wMonth, st.wDay,
+      st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
+    */
+    int written = _snprintf_s(buf, size, _TRUNCATE,
+                              "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                              st.wYear,
+                              st.wMonth,
+                              st.wDay,
+                              st.wHour,
+                              st.wMinute,
+                              st.wSecond,
+                              st.wMilliseconds);
+    if (written < 0)
+        return -1; /* Could not write into buffer */
+    return 0;
+
+#else
+
+    /*----------- macOS / Linux / Unix version -----------*/
+    struct timeval tv;
+    if (gettimeofday(&tv, NULL) != 0) {
+        return -1; /* gettimeofday failed */
+    }
+
+    struct tm local_tm;
+    localtime_r(&tv.tv_sec, &local_tm);
+
+    int ms = (int)(tv.tv_usec / 1000); /* convert microseconds to milliseconds */
+
+    /* 
+       local_tm.tm_year is years since 1900,
+       local_tm.tm_mon  is 0-based (0=Jan, 11=Dec) 
+    */
+    int written = snprintf(buf, size,
+                           "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                           local_tm.tm_year + 1900,
+                           local_tm.tm_mon + 1,
+                           local_tm.tm_mday,
+                           local_tm.tm_hour,
+                           local_tm.tm_min,
+                           local_tm.tm_sec,
+                           ms);
+    if (written < 0 || (size_t)written >= size) {
+        return -1; /* Buffer too small or formatting error */
+    }
+    return 0;
+
+#endif
+}
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
index 05c06f80fb..635bae6d5e 100644
--- a/fftools/graph/graphprint.c
+++ b/fftools/graph/graphprint.c
@@ -873,6 +873,11 @@ static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
 
     av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
 
+    if (show_graph) {
+        if (!print_graphs_format || strcmp(print_graphs_format, "mermaidhtml") != 0)
+            print_graphs_format = av_strdup("mermaidhtml");
+    }
+
     if (!print_graphs_format)
         print_graphs_format = av_strdup("json");
     if (!print_graphs_format) {
@@ -1097,5 +1102,46 @@ cleanup:
 
 int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
 {
-    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+    int ret;
+
+    if (show_graph) {
+        char buf[2048];
+        AVBPrint bp;
+
+        av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+        print_graphs = 0;
+
+        ret = ff_get_temp_dir(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error getting temp directory path for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        ret = ff_make_timestamped_html_name(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error creating temp file name for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        av_bprint_finalize(&bp, &print_graphs_file);
+    }
+
+    ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    if (!ret && show_graph) {
+        av_log(NULL, AV_LOG_INFO, "Execution graph saved as: %s\n", print_graphs_file);
+        av_log(NULL, AV_LOG_INFO, "Trying to launch graph in browser...\n");
+
+        ret = ff_open_html_in_browser(print_graphs_file);
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Browser could not be launched for execution graph display\nPlease open manually: %s\n", print_graphs_file);
+        }
+    }
+
+    return ret;
 }
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
index 9f043cc273..43f769870b 100644
--- a/fftools/graph/graphprint.h
+++ b/fftools/graph/graphprint.h
@@ -27,4 +27,36 @@ int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles,
 
 int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
 
+/**
+ * Open an HTML file in the default browser (Windows, macOS, Linux/Unix).
+ *
+ * @param html_path Absolute or relative path to the HTML file.
+ * @return 0 on success, -1 on failure.
+ *
+ * NOTE: This uses system() calls for non-Windows, and ShellExecute on Windows.
+ *       Exercise caution if 'html_path' is untrusted (possible command injection).
+ */
+int ff_open_html_in_browser(const char *html_path);
+
+/**
+ * Retrieve the system's temporary directory.
+ *
+ * @param buf  Output buffer to store the temp directory path (including trailing slash)
+ * @param size Size of the output buffer in bytes
+ * @return 0 on success, -1 on failure (buffer too small or other errors)
+ *
+ * Note: On most platforms, the path will include a trailing slash (e.g. "C:\\Users\\...\\Temp\\" on Windows, "/tmp/" on Unix).
+ */
+int ff_get_temp_dir(char *buf, size_t size);
+
+/**
+ * Create a timestamped HTML filename, e.g.:
+ *   ffmpeg_graph_2024-01-01_22-12-59_123.html
+ *
+ * @param buf  Pointer to buffer where the result is stored
+ * @param size Size of the buffer in bytes
+ * @return 0 on success, -1 on error (e.g. buffer too small)
+ */
+int ff_make_timestamped_html_name(char *buf, size_t size);
+
 #endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v6 12/13] fftools/graphprint: Add execution graph printing
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 12/13] fftools/graphprint: Add execution graph printing softworkz
@ 2025-04-25 22:26             ` Michael Niedermayer
  2025-04-25 23:17               ` softworkz .
  0 siblings, 1 reply; 130+ messages in thread
From: Michael Niedermayer @ 2025-04-25 22:26 UTC (permalink / raw)
  To: FFmpeg development discussions and patches


[-- Attachment #1.1: Type: text/plain, Size: 2166 bytes --]

On Thu, Apr 24, 2025 at 01:13:10AM +0000, softworkz wrote:
> From: softworkz <softworkz@hotmail.com>
> 
> The key benefits are:
> 
> - Different to other graph printing methods, this is outputting:
>   - all graphs with runtime state
>     (including auto-inserted filters)
>   - each graph with its inputs and outputs
>   - all filters with their in- and output pads
>   - all connections between all input- and output pads
>   - for each connection:
>     - the runtime-negotiated format and media type
>     - the hw context
>     - if video hw context, both: hw pixfmt + sw pixfmt
> - Output can either be printed to stdout or written to specified file
> - Output is machine-readable
> - Use the same output implementation as ffprobe, supporting multiple
>   formats
> 
> Signed-off-by: softworkz <softworkz@hotmail.com>
> ---
>  doc/ffmpeg.texi                   |   10 +
>  fftools/Makefile                  |   20 +-
>  fftools/ffmpeg.c                  |    4 +
>  fftools/ffmpeg.h                  |    3 +
>  fftools/ffmpeg_filter.c           |    5 +
>  fftools/ffmpeg_opt.c              |   13 +
>  fftools/graph/graphprint.c        | 1101 +++++++++++++++++++++++++++++
>  fftools/graph/graphprint.h        |   30 +
>  fftools/textformat/avtextformat.c |    2 +
>  fftools/textformat/avtextformat.h |   29 +
>  fftools/textformat/tf_mermaid.c   |  658 +++++++++++++++++
>  fftools/textformat/tf_mermaid.h   |   41 ++
>  12 files changed, 1915 insertions(+), 1 deletion(-)
>  create mode 100644 fftools/graph/graphprint.c
>  create mode 100644 fftools/graph/graphprint.h
>  create mode 100644 fftools/textformat/tf_mermaid.c
>  create mode 100644 fftools/textformat/tf_mermaid.h

make
LD	ffmpeg_g.exe
x86_64-w64-mingw32-gcc: error: fftools/resources/graph.html.o: No such file or directory
x86_64-w64-mingw32-gcc: error: fftools/resources/graph.css.o: No such file or directory
make: *** [Makefile:142: ffmpeg_g.exe] Error 1

[...]
-- 
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

I am the wisest man alive, for I know one thing, and that is that I know
nothing. -- Socrates

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

[-- Attachment #2: Type: text/plain, Size: 251 bytes --]

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* Re: [FFmpeg-devel] [PATCH v6 12/13] fftools/graphprint: Add execution graph printing
  2025-04-25 22:26             ` Michael Niedermayer
@ 2025-04-25 23:17               ` softworkz .
  0 siblings, 0 replies; 130+ messages in thread
From: softworkz . @ 2025-04-25 23:17 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Michael Niedermayer
> Sent: Samstag, 26. April 2025 00:27
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH v6 12/13] fftools/graphprint: Add
> execution graph printing
> 
> On Thu, Apr 24, 2025 at 01:13:10AM +0000, softworkz wrote:
> > From: softworkz <softworkz@hotmail.com>
> >
> > The key benefits are:
> >
> > - Different to other graph printing methods, this is outputting:
> >   - all graphs with runtime state
> >     (including auto-inserted filters)
> >   - each graph with its inputs and outputs
> >   - all filters with their in- and output pads
> >   - all connections between all input- and output pads
> >   - for each connection:
> >     - the runtime-negotiated format and media type
> >     - the hw context
> >     - if video hw context, both: hw pixfmt + sw pixfmt
> > - Output can either be printed to stdout or written to specified
> file
> > - Output is machine-readable
> > - Use the same output implementation as ffprobe, supporting multiple
> >   formats
> >
> > Signed-off-by: softworkz <softworkz@hotmail.com>
> > ---
> >  doc/ffmpeg.texi                   |   10 +
> >  fftools/Makefile                  |   20 +-
> >  fftools/ffmpeg.c                  |    4 +
> >  fftools/ffmpeg.h                  |    3 +
> >  fftools/ffmpeg_filter.c           |    5 +
> >  fftools/ffmpeg_opt.c              |   13 +
> >  fftools/graph/graphprint.c        | 1101
> +++++++++++++++++++++++++++++
> >  fftools/graph/graphprint.h        |   30 +
> >  fftools/textformat/avtextformat.c |    2 +
> >  fftools/textformat/avtextformat.h |   29 +
> >  fftools/textformat/tf_mermaid.c   |  658 +++++++++++++++++
> >  fftools/textformat/tf_mermaid.h   |   41 ++
> >  12 files changed, 1915 insertions(+), 1 deletion(-)
> >  create mode 100644 fftools/graph/graphprint.c
> >  create mode 100644 fftools/graph/graphprint.h
> >  create mode 100644 fftools/textformat/tf_mermaid.c
> >  create mode 100644 fftools/textformat/tf_mermaid.h
> 
> make
> LD	ffmpeg_g.exe
> x86_64-w64-mingw32-gcc: error: fftools/resources/graph.html.o: No such
> file or directory
> x86_64-w64-mingw32-gcc: error: fftools/resources/graph.css.o: No such
> file or directory
> make: *** [Makefile:142: ffmpeg_g.exe] Error 1
> 
> [...]
> --
> Michael     GnuPG fingerprint:

Oops - I had tested out-of-tree explicitly before, but seems it got lost
again.

Thanks
sw

_______________________________________________
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing
  2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
                             ` (12 preceding siblings ...)
  2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 13/13] fftools/graphprint: Now, make it a Killer-Feature! softworkz
@ 2025-04-25 23:30           ` ffmpegagent
  2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 01/13] fftools/textformat: Formatting and whitespace changes softworkz
                               ` (12 more replies)
  13 siblings, 13 replies; 130+ messages in thread
From: ffmpegagent @ 2025-04-25 23:30 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

Shortest cover letter for my longest-running FFmpeg patchset:

 * Apply
 * Build
 * Add the "-sg" switch to any FFmpeg command line
 * Press 'q' when you don't want to wait

SG = Show Graph

Documentation and examples can be found here:

https://github.com/softworkz/ffmpeg_output_apis/wiki


Version Updates
===============


V2
==

 * Rebased on top of Andreas' improvements
 * Applied changes from review (thanks, Andreas)


V3
==

 * Fixed all "new warnings"
 * Fixed out-of-tree building (thanks, Michael)


V4
==

 * Resolved merge conflict
 * Fixed build on MinGW (missing include due to WIN32_LEAN_AND_MEAN being
   defined) (thanks, Michael)


V5
==

 * Applied changes as per review from Stefano (thanks!)
 * Introduced AVTextFormatOptions struct for options in
   avtext_context_open()


V6
==

 * Fix "new warning" in 2nd last commit
 * Squash patches 04 and 05 (they weren't truely independent)
 * Applied changes as per review from Stefano (thanks!)


V7
==

 * Bitten by OOT builds once again (thanks, Michael)

.

softworkz (13):
  fftools/textformat: Formatting and whitespace changes
  fftools/textformat: Apply quality improvements
  fftools/avtextformat: Re-use BPrint in loop
  fftools/textformat: Introduce AVTextFormatOptions for
    avtext_context_open()
  fftools/textformat: Introduce common header and deduplicate code
  fftools/tf_internal: Use av_default_item_name
  fftools/textformat: Add function avtext_print_integer_flags()
  fftools/ffmpeg_filter: Move some declaration to new header file
  avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  fftools/resources: Add resource manager files
  fftools/ffmpeg_mux: Make ms_from_ost() inline
  fftools/graphprint: Add execution graph printing
  fftools/graphprint: Now, make it a Killer-Feature!

 doc/APIchanges                     |    3 +
 doc/ffmpeg.texi                    |   14 +
 ffbuild/common.mak                 |   28 +-
 fftools/Makefile                   |   22 +-
 fftools/ffmpeg.c                   |    4 +
 fftools/ffmpeg.h                   |    4 +
 fftools/ffmpeg_filter.c            |  195 +----
 fftools/ffmpeg_filter.h            |  234 ++++++
 fftools/ffmpeg_mux.h               |    2 +-
 fftools/ffmpeg_opt.c               |   17 +
 fftools/ffprobe.c                  |   13 +-
 fftools/graph/filelauncher.c       |  205 +++++
 fftools/graph/graphprint.c         | 1147 ++++++++++++++++++++++++++++
 fftools/graph/graphprint.h         |   62 ++
 fftools/resources/.gitignore       |    4 +
 fftools/resources/Makefile         |   27 +
 fftools/resources/graph.css        |  353 +++++++++
 fftools/resources/graph.html       |   86 +++
 fftools/resources/resman.c         |  213 ++++++
 fftools/resources/resman.h         |   50 ++
 fftools/textformat/avtextformat.c  |  228 +++---
 fftools/textformat/avtextformat.h  |   67 +-
 fftools/textformat/avtextwriters.h |   11 +-
 fftools/textformat/tf_compact.c    |  121 +--
 fftools/textformat/tf_default.c    |   55 +-
 fftools/textformat/tf_flat.c       |   51 +-
 fftools/textformat/tf_ini.c        |   62 +-
 fftools/textformat/tf_internal.h   |   81 ++
 fftools/textformat/tf_json.c       |   56 +-
 fftools/textformat/tf_mermaid.c    |  658 ++++++++++++++++
 fftools/textformat/tf_mermaid.h    |   41 +
 fftools/textformat/tf_xml.c        |   68 +-
 fftools/textformat/tw_avio.c       |   20 +-
 fftools/textformat/tw_buffer.c     |    9 +-
 fftools/textformat/tw_stdout.c     |   10 +-
 libavfilter/avfilter.c             |    9 +
 libavfilter/avfilter.h             |   12 +
 37 files changed, 3682 insertions(+), 560 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h
 create mode 100644 fftools/graph/filelauncher.c
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h
 create mode 100644 fftools/textformat/tf_internal.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h


base-commit: 25b0a8e295749a60a238ba0d6fe7a3742937b6bb
Published-As: https://github.com/ffstaging/FFmpeg/releases/tag/pr-ffstaging-66%2Fsoftworkz%2Fsubmit_print_execution_graph-v7
Fetch-It-Via: git fetch https://github.com/ffstaging/FFmpeg pr-ffstaging-66/softworkz/submit_print_execution_graph-v7
Pull-Request: https://github.com/ffstaging/FFmpeg/pull/66

Range-diff vs v6:

  1:  b4bb8cdcc6 =  1:  b4bb8cdcc6 fftools/textformat: Formatting and whitespace changes
  2:  1a4044ba23 =  2:  1a4044ba23 fftools/textformat: Apply quality improvements
  3:  5972ecf213 =  3:  5972ecf213 fftools/avtextformat: Re-use BPrint in loop
  4:  97ab9e0426 =  4:  97ab9e0426 fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open()
  5:  6d1cf2b3fd =  5:  6d1cf2b3fd fftools/textformat: Introduce common header and deduplicate code
  6:  fa22ead3ef =  6:  fa22ead3ef fftools/tf_internal: Use av_default_item_name
  7:  59dfd3ded6 =  7:  59dfd3ded6 fftools/textformat: Add function avtext_print_integer_flags()
  8:  55a704faa5 =  8:  55a704faa5 fftools/ffmpeg_filter: Move some declaration to new header file
  9:  b6320cab8c =  9:  b6320cab8c avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
 10:  0e3e9b3e40 ! 10:  48d36d9df0 fftools/resources: Add resource manager files
     @@ fftools/resources/Makefile (new)
      +	$(RM) $(CLEANSUFFIXES:%=fftools/resources/%)
      +
      +
     -+HTML_RESOURCES := fftools/resources/graph.html \
     ++HTML_RESOURCES := $(SRC_PATH)/fftools/resources/graph.html \
      +
      +# .html => (gzip) .html.gz => (bin2c) .html.c => (cc) .o
      +HTML_RESOURCES_GZ := $(HTML_RESOURCES:.html=.html.gz)
      +HTML_RESOURCES_C := $(HTML_RESOURCES_GZ:.html.gz=.html.c)
      +HTML_RESOURCES_OBJS := $(HTML_RESOURCES_C:.c=.o)
      +
     -+CSS_RESOURCES := fftools/resources/graph.css   \
     ++CSS_RESOURCES := $(SRC_PATH)/fftools/resources/graph.css   \
      +
      +# .css => (sh) .css.min  => (gzip) .css.min.gz => (bin2c) .css.c => (cc) .o
      +CSS_RESOURCES_MIN := $(CSS_RESOURCES:.css=.css.min)
     @@ fftools/resources/Makefile (new)
      +CSS_RESOURCES_OBJS := $(CSS_RESOURCES_C:.c=.o)
      +
      +# Uncomment to prevent deletion
     -+#.PRECIOUS: %.css.c %.css.min %.css.gz  %.css.min.gz
     ++#.PRECIOUS: %.css.c %.css.min %.css.gz %.css.min.gz %.html.gz %.html.c
      +
      +OBJS-resman +=                  \
      +    fftools/resources/resman.o          \
 11:  9464b8d9f4 = 11:  c4c7340c1d fftools/ffmpeg_mux: Make ms_from_ost() inline
 12:  2b271af447 = 12:  9cc44e3690 fftools/graphprint: Add execution graph printing
 13:  a23cc583de = 13:  1cb17c6e3b fftools/graphprint: Now, make it a Killer-Feature!

-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 01/13] fftools/textformat: Formatting and whitespace changes
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
@ 2025-04-25 23:30             ` softworkz
  2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 02/13] fftools/textformat: Apply quality improvements softworkz
                               ` (11 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:30 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c  | 86 ++++++++++++++--------------
 fftools/textformat/avtextformat.h  | 16 +++---
 fftools/textformat/avtextwriters.h | 11 ++--
 fftools/textformat/tf_compact.c    | 91 +++++++++++++++++-------------
 fftools/textformat/tf_default.c    | 20 +++----
 fftools/textformat/tf_flat.c       | 26 +++++----
 fftools/textformat/tf_ini.c        | 36 ++++++------
 fftools/textformat/tf_json.c       | 10 ++--
 fftools/textformat/tf_xml.c        | 30 +++++-----
 9 files changed, 172 insertions(+), 154 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 9200b9b1ad..74d179c516 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -34,9 +34,9 @@
 #include "libavutil/opt.h"
 #include "avtextformat.h"
 
-#define SECTION_ID_NONE -1
+#define SECTION_ID_NONE (-1)
 
-#define SHOW_OPTIONAL_FIELDS_AUTO       -1
+#define SHOW_OPTIONAL_FIELDS_AUTO      (-1)
 #define SHOW_OPTIONAL_FIELDS_NEVER       0
 #define SHOW_OPTIONAL_FIELDS_ALWAYS      1
 
@@ -64,14 +64,14 @@ static const char *textcontext_get_formatter_name(void *p)
 
 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" },
+      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"}},
+      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 }
 };
 
@@ -126,7 +126,7 @@ void avtext_context_close(AVTextFormatContext **ptctx)
 
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
@@ -200,7 +200,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash) {
+    if (show_data_hash)
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -211,7 +211,6 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             return ret;
         }
-    }
 
     /* validate replace string */
     {
@@ -224,7 +223,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             if (ret < 0) {
                 AVBPrint bp;
                 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
-                bprint_bytes(&bp, p0, p-p0),
+                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);
@@ -248,15 +247,13 @@ fail:
 }
 
 /* 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_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)
+void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
@@ -272,8 +269,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx,
 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;
+    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]++;
@@ -285,8 +283,7 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
     tctx->level--;
 }
 
-void avtext_print_integer(AVTextFormatContext *tctx,
-                                        const char *key, int64_t val)
+void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
     const struct AVTextFormatSection *section = tctx->section[tctx->level];
 
@@ -324,11 +321,9 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
             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);
+                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);
@@ -340,11 +335,10 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
             av_bprint_append_data(&dstbuf, p0, p-p0);
     }
 
-    if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
+    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);
@@ -352,7 +346,11 @@ end:
 }
 
 struct unit_value {
-    union { double d; int64_t i; } val;
+    union {
+        double  d;
+        int64_t i;
+    } val;
+
     const char *unit;
 };
 
@@ -402,8 +400,9 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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 : "");
+                    prefix_string, tctx->show_value_unit ? uv.unit : "");
     }
 
     return buf;
@@ -427,8 +426,8 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 
     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)))
+            && (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)) {
@@ -440,11 +439,10 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
             if (ret < 0) goto end;
             tctx->formatter->print_string(tctx, key1, val1);
         end:
-            if (ret < 0) {
+            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 {
@@ -457,8 +455,7 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
     return ret;
 }
 
-void avtext_print_rational(AVTextFormatContext *tctx,
-                                         const char *key, AVRational q, char sep)
+void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep)
 {
     char buf[44];
     snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
@@ -466,7 +463,7 @@ void avtext_print_rational(AVTextFormatContext *tctx,
 }
 
 void avtext_print_time(AVTextFormatContext *tctx, const char *key,
-                              int64_t ts, const AVRational *time_base, int is_duration)
+                       int64_t ts, const AVRational *time_base, int is_duration)
 {
     char buf[128];
 
@@ -484,15 +481,14 @@ void avtext_print_time(AVTextFormatContext *tctx, const char *key,
 
 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)) {
+    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0))
         avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
-    } else {
+    else
         avtext_print_integer(tctx, key, ts);
-    }
 }
 
 void avtext_print_data(AVTextFormatContext *tctx, const char *name,
-                              const uint8_t *data, int size)
+                       const uint8_t *data, int size)
 {
     AVBPrint bp;
     int offset = 0, l, i;
@@ -520,12 +516,13 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 }
 
 void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
-                                   const uint8_t *data, int size)
+                            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));
@@ -551,7 +548,7 @@ void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
             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 --;
+            size--;
         }
         av_bprintf(&bp, "\n");
         offset += offset_add;
@@ -641,7 +638,8 @@ fail:
     return ret;
 }
 
-static const AVTextFormatter *registered_formatters[7+1];
+static const AVTextFormatter *registered_formatters[10 + 1];
+
 static void formatters_register_all(void)
 {
     static int initialized;
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c2c56dc1a7..c598af3450 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -86,17 +86,17 @@ typedef struct AVTextFormatter {
 #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
+    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
+    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
+    const AVTextFormatSection *sections; ///< array containing all sections
+    int nb_sections;                   ///< number of sections
 
-    int level;                      ///< current level, starting from 0
+    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];
diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index c99d6b3548..34db3f1832 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -37,11 +37,11 @@ typedef struct AVTextWriter {
     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, ...);
+    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 {
@@ -49,7 +49,6 @@ typedef struct AVTextWriterContext {
     const AVTextWriter *writer;
     const char *name;
     void *priv;                     ///< private data for use by the writer
-
 } AVTextWriterContext;
 
 
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index 31bfc81513..d4ac296a42 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -58,10 +58,10 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 
     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 '\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)
@@ -78,6 +78,7 @@ static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep,
 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)
@@ -114,16 +115,16 @@ typedef struct 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},
+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);
@@ -139,10 +140,13 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
     }
     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 {
+    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);
     }
@@ -162,8 +166,8 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
     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))))) {
+            (!(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 */
@@ -171,10 +175,10 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
         AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
 
         compact->nested_section[wctx->level] = 1;
-        compact->has_nested_elems[wctx->level-1] = 1;
+        compact->has_nested_elems[wctx->level - 1] = 1;
 
         av_bprintf(section_pbuf, "%s%s",
-                   wctx->section_pbuf[wctx->level-1].str, element_name);
+                   wctx->section_pbuf[wctx->level - 1].str, element_name);
 
         if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
             // add /TYPE to prefix
@@ -185,30 +189,33 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
                 char c =
                     (*p >= '0' && *p <= '9') ||
                     (*p >= 'a' && *p <= 'z') ||
-                    (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
+                    (*p >= 'A' && *p <= 'Z')
+                    ? (char)(char)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];
+        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])
+        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)))
+            !(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)
 {
+    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     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)))
+        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
         writer_w8(wctx, '\n');
 }
 
@@ -217,9 +224,12 @@ static void compact_print_str(AVTextFormatContext *wctx, const char *key, const
     CompactContext *compact = wctx->priv;
     AVBPrint buf;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
@@ -229,9 +239,12 @@ static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_
 {
     CompactContext *compact = wctx->priv;
 
-    if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
+    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);
 }
 
@@ -253,15 +266,15 @@ const AVTextFormatter avtextformatter_compact = {
 #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},
+    { "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);
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 86582829e4..2c5047eafd 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -56,11 +56,11 @@ typedef struct DefaultContext {
 #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},
+    { "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);
@@ -69,7 +69,7 @@ DEFINE_FORMATTER_CLASS(default);
 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++)
+    for (i = 0; src[i] && i < dst_size - 1; i++)
         dst[i] = av_toupper(src[i]);
     dst[i] = 0;
     return dst;
@@ -85,10 +85,10 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 
     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))) {
+        !(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,
+                   wctx->section_pbuf[wctx->level - 1].str,
                    upcase_string(buf, sizeof(buf),
                                  av_x_if_null(section->element_name, section->name)));
     }
@@ -96,7 +96,7 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
     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)))
+    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));
 }
 
@@ -109,7 +109,7 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     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)))
+    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));
 }
 
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index 919d44bc6b..f692971bcc 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -57,12 +57,12 @@ typedef struct 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},
+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);
@@ -126,16 +126,18 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
     av_bprint_clear(buf);
     if (!parent_section)
         return;
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+
+    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))) {
+        !(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];
+            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);
         }
     }
@@ -166,6 +168,6 @@ const AVTextFormatter avtextformatter_flat = {
     .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,
+    .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
index d8099ff92e..88add0819a 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -64,9 +64,9 @@ typedef struct INIContext {
 #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},
+    { "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);
@@ -74,9 +74,9 @@ DEFINE_FORMATTER_CLASS(ini);
 static char *ini_escape_str(AVBPrint *dst, const char *src)
 {
     int i = 0;
-    char c = 0;
+    char c;
 
-    while (c = src[i++]) {
+    while ((c = src[i++])) {
         switch (c) {
         case '\b': av_bprintf(dst, "%s", "\\b"); break;
         case '\f': av_bprintf(dst, "%s", "\\f"); break;
@@ -84,9 +84,11 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
         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);
+        case '#':
+        case '=':
+        case ':':
+            av_bprint_chars(dst, '\\', 1);
+            /* fallthrough */
         default:
             if ((unsigned char)c < 32)
                 av_bprintf(dst, "\\x00%02x", c & 0xff);
@@ -112,23 +114,23 @@ static void ini_print_section_header(AVTextFormatContext *wctx, const void *data
         return;
     }
 
-    if (wctx->nb_item[wctx->level-1])
+    if (wctx->nb_item[wctx->level - 1])
         writer_w8(wctx, '\n');
 
-    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
+    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))) {
+        !(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);
+            unsigned 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, ".%u", n);
         }
     }
 
-    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
+    if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
         writer_printf(wctx, "[%s]\n", buf->str);
 }
 
@@ -154,6 +156,6 @@ const AVTextFormatter avtextformatter_ini = {
     .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,
+    .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
index c26a912435..b61d3740c6 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -56,9 +56,9 @@ typedef struct 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 },
+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 }
 };
 
@@ -76,8 +76,8 @@ static av_cold int json_init(AVTextFormatContext *wctx)
 
 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};
+    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++) {
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index 6c89d01e9d..befb39246d 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -58,11 +58,11 @@ typedef struct XMLContext {
 #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},
+    { "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);
@@ -104,8 +104,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 
         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 : "");
+                      xml->fully_qualified ? "ffprobe:" : "",
+                      xml->fully_qualified ? qual : "");
         return;
     }
 
@@ -115,12 +115,13 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
     }
 
     if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
-        wctx->level && wctx->nb_item[wctx->level-1])
+        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_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;
@@ -131,7 +132,8 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
         }
         writer_printf(wctx, ">\n", section->name);
     } else {
-        XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "<%s ", section->name);
         xml->within_tag = 1;
     }
 }
@@ -148,7 +150,8 @@ static void xml_print_section_footer(AVTextFormatContext *wctx)
         writer_put_str(wctx, "/>\n");
         xml->indent_level--;
     } else {
-        XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
+        XML_INDENT();
+        writer_printf(wctx, "</%s>\n", section->name);
         xml->indent_level--;
     }
 }
@@ -195,7 +198,8 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
     av_bprint_finalize(&buf, NULL);
 }
 
-static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
+static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
+{
     xml_print_value(wctx, key, value, 0, 0);
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 02/13] fftools/textformat: Apply quality improvements
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
  2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 01/13] fftools/textformat: Formatting and whitespace changes softworkz
@ 2025-04-25 23:30             ` softworkz
  2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 03/13] fftools/avtextformat: Re-use BPrint in loop softworkz
                               ` (10 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:30 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Perform multiple improvements to increase code robustness.
In particular:
- favor unsigned counters for loops
- add missing checks
- avoid possible leaks
- move variable declarations to inner scopes when feasible
- provide explicit type-casting when needed

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 85 ++++++++++++++++++++-----------
 fftools/textformat/avtextformat.h |  6 +--
 fftools/textformat/tf_default.c   |  8 ++-
 fftools/textformat/tf_ini.c       |  2 +-
 fftools/textformat/tf_json.c      | 17 ++++---
 fftools/textformat/tf_xml.c       |  3 --
 fftools/textformat/tw_avio.c      | 11 +++-
 7 files changed, 83 insertions(+), 49 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 74d179c516..1939a1f739 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -93,9 +93,8 @@ static const AVClass textcontext_class = {
 
 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++)
+    for (unsigned i = 0; i < ubuf_size; i++)
         av_bprintf(bp, "%02X", ubuf[i]);
 }
 
@@ -137,6 +136,9 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
     AVTextFormatContext *tctx;
     int i, ret = 0;
 
+    if (!ptctx || !formatter)
+        return AVERROR(EINVAL);
+
     if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
@@ -209,25 +211,26 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
                     av_log(NULL, AV_LOG_ERROR, " %s", n);
                 av_log(NULL, AV_LOG_ERROR, "\n");
             }
-            return ret;
+            goto fail;
         }
 
     /* validate replace string */
     {
-        const uint8_t *p = tctx->string_validation_replacement;
-        const uint8_t *endp = p + strlen(p);
+        const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
+        const uint8_t *endp = p + strlen((const char *)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);
+                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
                 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;
+                av_bprint_finalize(&bp, NULL);
+                goto fail;
             }
         }
     }
@@ -255,6 +258,9 @@ static const char unit_bit_per_second_str[] = "bit/s";
 
 void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id)
 {
+    if (section_id < 0 || section_id >= tctx->nb_sections)
+        return;
+
     tctx->level++;
     av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
 
@@ -268,6 +274,9 @@ void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, in
 
 void avtext_print_section_footer(AVTextFormatContext *tctx)
 {
+    if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS)
+        return;
+
     int section_id = tctx->section[tctx->level]->id;
     int parent_section_id = tctx->level
         ? tctx->section[tctx->level - 1]->id
@@ -285,7 +294,11 @@ void avtext_print_section_footer(AVTextFormatContext *tctx)
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
+
+    av_assert0(key && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
+
+    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);
@@ -354,17 +367,18 @@ struct unit_value {
     const char *unit;
 };
 
-static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
+static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
 {
     double vald;
-    int64_t vali;
+    int64_t vali = 0;
     int show_float = 0;
 
     if (uv.unit == unit_second_str) {
         vald = uv.val.d;
         show_float = 1;
     } else {
-        vald = vali = uv.val.i;
+        vald = (double)uv.val.i;
+        vali = uv.val.i;
     }
 
     if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
@@ -383,17 +397,17 @@ static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, st
             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);
+                index = (int64_t)(log2(vald) / 10);
+                index = av_clip64(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);
+                index = (int64_t)(log10(vald) / 3);
+                index = av_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
                 vald /= si_prefixes[index].dec_val;
                 prefix_string = si_prefixes[index].dec_str;
             }
-            vali = vald;
+            vali = (int64_t)vald;
         }
 
         if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
@@ -421,9 +435,13 @@ void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value
 
 int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
 {
-    const struct AVTextFormatSection *section = tctx->section[tctx->level];
+    const AVTextFormatSection *section;
     int ret = 0;
 
+    av_assert0(key && val && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
+
+    section = tctx->section[tctx->level];
+
     if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
         (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
             && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
@@ -465,12 +483,11 @@ void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRationa
 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);
+        char buf[128];
+        double d = av_q2d(*time_base) * ts;
         struct unit_value uv;
         uv.val.d = d;
         uv.unit = unit_second_str;
@@ -491,7 +508,8 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
                        const uint8_t *data, int size)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -518,25 +536,29 @@ void avtext_print_data(AVTextFormatContext *tctx, const char *name,
 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 };
+    char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
+    int len;
 
     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);
+    len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
+    av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
     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)
+                           uint8_t *data, int size, const char *format,
+                           int columns, int bytes, int offset_add)
 {
     AVBPrint bp;
-    int offset = 0, l, i;
+    unsigned offset = 0;
+    int l, i;
+
+    if (!name || !data || !format || columns <= 0 || bytes <= 0)
+        return;
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
     av_bprintf(&bp, "\n");
@@ -602,12 +624,15 @@ int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *w
     AVTextWriterContext *wctx;
     int ret = 0;
 
-    if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
+    if (!pwctx || !writer)
+        return AVERROR(EINVAL);
+
+    if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
-    if (!(wctx->priv = av_mallocz(writer->priv_size))) {
+    if (writer->priv_size && !((wctx->priv = av_mallocz(writer->priv_size)))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index c598af3450..aea691f351 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -21,9 +21,7 @@
 #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"
@@ -103,7 +101,7 @@ struct AVTextFormatContext {
     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];
+    const AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
     AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
                                                   ///  used by various formatters
 
@@ -124,7 +122,7 @@ struct AVTextFormatContext {
 #define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const struct AVTextFormatSection *sections, int nb_sections,
+                        const AVTextFormatSection *sections, int nb_sections,
                         int show_value_unit,
                         int use_value_prefix,
                         int use_byte_value_binary_prefix,
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 2c5047eafd..ad97173b0b 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -68,9 +68,10 @@ 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;
+    unsigned i;
+
     for (i = 0; src[i] && i < dst_size - 1; i++)
-        dst[i] = av_toupper(src[i]);
+        dst[i] = (char)av_toupper(src[i]);
     dst[i] = 0;
     return dst;
 }
@@ -106,6 +107,9 @@ static void default_print_section_footer(AVTextFormatContext *wctx)
     const struct AVTextFormatSection *section = wctx->section[wctx->level];
     char buf[32];
 
+    if (!section)
+        return;
+
     if (def->noprint_wrappers || def->nested_section[wctx->level])
         return;
 
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index 88add0819a..dd77d0e8bf 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -91,7 +91,7 @@ static char *ini_escape_str(AVBPrint *dst, const char *src)
             /* fallthrough */
         default:
             if ((unsigned char)c < 32)
-                av_bprintf(dst, "\\x00%02x", c & 0xff);
+                av_bprintf(dst, "\\x00%02x", (unsigned char)c);
             else
                 av_bprint_chars(dst, c, 1);
             break;
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index b61d3740c6..50c3d90440 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -80,13 +80,18 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
     static const char json_subst[]  = { '"', '\\',  'b',  'f',  'n',  'r',  't', 0 };
     const char *p;
 
+    if (!src) {
+        av_log(log_ctx, AV_LOG_WARNING, "Cannot escape NULL string, returning NULL\n");
+        return NULL;
+    }
+
     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);
+            av_bprintf(dst, "\\u00%02x", (unsigned char)*p);
         } else {
             av_bprint_chars(dst, *p, 1);
         }
@@ -100,11 +105,10 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *parent_section = wctx->level ? wctx->section[wctx->level - 1] : NULL;
 
-    if (wctx->level && wctx->nb_item[wctx->level-1])
+    if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
 
     if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
@@ -185,8 +189,7 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 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;
+    const 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))
diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c
index befb39246d..28abfc6400 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -18,10 +18,7 @@
  * 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"
diff --git a/fftools/textformat/tw_avio.c b/fftools/textformat/tw_avio.c
index 6034f74ec9..29889598bb 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -23,6 +23,7 @@
 #include <string.h>
 
 #include "avtextwriters.h"
+#include "libavutil/avassert.h"
 
 #include "libavutil/error.h"
 
@@ -53,7 +54,7 @@ static void io_w8(AVTextWriterContext *wctx, int b)
 static void io_put_str(AVTextWriterContext *wctx, const char *str)
 {
     IOWriterContext *ctx = wctx->priv;
-    avio_write(ctx->avio_context, str, strlen(str));
+    avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
 static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
@@ -78,10 +79,14 @@ const AVTextWriter avtextwriter_avio = {
 
 int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
 {
+    if (!output_filename || !output_filename[0]) {
+        av_log(NULL, AV_LOG_ERROR, "The output_filename cannot be NULL or empty\n");
+        return AVERROR(EINVAL);
+    }
+
     IOWriterContext *ctx;
     int ret;
 
-
     ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
     if (ret < 0)
         return ret;
@@ -103,6 +108,8 @@ int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_fil
 
 int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
 {
+    av_assert0(avio_ctx);
+
     IOWriterContext *ctx;
     int 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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 03/13] fftools/avtextformat: Re-use BPrint in loop
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
  2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 01/13] fftools/textformat: Formatting and whitespace changes softworkz
  2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 02/13] fftools/textformat: Apply quality improvements softworkz
@ 2025-04-25 23:30             ` softworkz
  2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 04/13] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
                               ` (9 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:30 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Instead of initializing a new BPrint in each iteration of
the loop, re-use the same BPrint struct and just clear it
for each iteration.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 1939a1f739..5ba427d6f2 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -308,24 +308,28 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
 
 static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
 {
-    const uint8_t *p, *endp;
+    const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
     AVBPrint dstbuf;
+    AVBPrint bp_invalid_seq;
     int invalid_chars_nb = 0, ret = 0;
 
+    *dstp = NULL;
     av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprint_init(&bp_invalid_seq, 0, AV_BPRINT_SIZE_UNLIMITED);
 
-    endp = src + strlen(src);
-    for (p = src; *p;) {
-        uint32_t code;
+    endp = srcp + strlen(src);
+    for (p = srcp; *p;) {
+        int32_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);
+
+            av_bprint_clear(&bp_invalid_seq);
+
+            bprint_bytes(&bp_invalid_seq, p0, p - p0);
+
+            av_log(tctx, AV_LOG_DEBUG, "Invalid UTF-8 sequence %s found in string '%s'\n", bp_invalid_seq.str, src);
             invalid = 1;
         }
 
@@ -355,6 +359,7 @@ static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const
 
 end:
     av_bprint_finalize(&dstbuf, dstp);
+    av_bprint_finalize(&bp_invalid_seq, NULL);
     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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 04/13] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open()
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (2 preceding siblings ...)
  2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 03/13] fftools/avtextformat: Re-use BPrint in loop softworkz
@ 2025-04-25 23:30             ` softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 05/13] fftools/textformat: Introduce common header and deduplicate code softworkz
                               ` (8 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:30 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This allows future addition of options without
changes to the signature of avtext_context_open().

Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffprobe.c                 | 13 +++++++++----
 fftools/textformat/avtextformat.c | 21 ++++++++-------------
 fftools/textformat/avtextformat.h | 16 +++++++++-------
 3 files changed, 26 insertions(+), 24 deletions(-)

diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index f5c83925b9..1277b1e4f9 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -3168,10 +3168,15 @@ int main(int argc, char **argv)
     if (ret < 0)
         goto end;
 
-    if ((ret = avtext_context_open(&tctx, f, wctx, f_args,
-                           sections, FF_ARRAY_ELEMS(sections), show_value_unit,
-                            use_value_prefix, use_byte_value_binary_prefix, use_value_sexagesimal_format,
-                            show_optional_fields, show_data_hash)) >= 0) {
+    AVTextFormatOptions tf_options = {
+        .show_optional_fields = show_optional_fields,
+        .show_value_unit = show_value_unit,
+        .use_value_prefix = use_value_prefix,
+        .use_byte_value_binary_prefix = use_byte_value_binary_prefix,
+        .use_value_sexagesimal_format = use_value_sexagesimal_format,
+    };
+
+    if ((ret = avtext_context_open(&tctx, f, wctx, f_args, sections, FF_ARRAY_ELEMS(sections), tf_options, show_data_hash)) >= 0) {
         if (f == &avtextformatter_xml)
             tctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;
 
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 5ba427d6f2..b8e283d00c 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -125,13 +125,7 @@ void avtext_context_close(AVTextFormatContext **ptctx)
 
 
 int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
-                        const 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)
+                        const AVTextFormatSection *sections, int nb_sections, AVTextFormatOptions options, char *show_data_hash)
 {
     AVTextFormatContext *tctx;
     int i, ret = 0;
@@ -155,11 +149,11 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         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;
+    tctx->show_value_unit = options.show_value_unit;
+    tctx->use_value_prefix = options.use_value_prefix;
+    tctx->use_byte_value_binary_prefix = options.use_byte_value_binary_prefix;
+    tctx->use_value_sexagesimal_format = options.use_value_sexagesimal_format;
+    tctx->show_optional_fields = options.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);
@@ -202,7 +196,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
         av_dict_free(&opts);
     }
 
-    if (show_data_hash)
+    if (show_data_hash) {
         if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
             if (ret == AVERROR(EINVAL)) {
                 const char *n;
@@ -213,6 +207,7 @@ int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *form
             }
             goto fail;
         }
+    }
 
     /* validate replace string */
     {
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index aea691f351..071149c5be 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -118,17 +118,19 @@ struct AVTextFormatContext {
     unsigned int string_validation_utf8_flags;
 };
 
+typedef struct AVTextFormatOptions {
+    int show_optional_fields;
+    int show_value_unit;
+    int use_value_prefix;
+    int use_byte_value_binary_prefix;
+    int use_value_sexagesimal_format;
+} AVTextFormatOptions;
+
 #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_context, const char *args,
-                        const 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);
+                        const AVTextFormatSection *sections, int nb_sections, AVTextFormatOptions options, char *show_data_hash);
 
 void avtext_context_close(AVTextFormatContext **tctx);
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 05/13] fftools/textformat: Introduce common header and deduplicate code
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (3 preceding siblings ...)
  2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 04/13] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
@ 2025-04-25 23:31             ` softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 06/13] fftools/tf_internal: Use av_default_item_name softworkz
                               ` (7 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Also change writer_printf signature in AVTextWriter to use va_list,
so that it can be called by the new function writer_printf()
in tf_internal.h.

Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextwriters.h |  2 +-
 fftools/textformat/tf_compact.c    | 32 ++++-------
 fftools/textformat/tf_default.c    | 27 +++-------
 fftools/textformat/tf_flat.c       | 25 +++------
 fftools/textformat/tf_ini.c        | 24 +++------
 fftools/textformat/tf_internal.h   | 85 ++++++++++++++++++++++++++++++
 fftools/textformat/tf_json.c       | 35 +++++-------
 fftools/textformat/tf_xml.c        | 35 +++++-------
 fftools/textformat/tw_avio.c       |  9 ++--
 fftools/textformat/tw_buffer.c     |  9 ++--
 fftools/textformat/tw_stdout.c     | 10 ++--
 11 files changed, 152 insertions(+), 141 deletions(-)
 create mode 100644 fftools/textformat/tf_internal.h

diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h
index 34db3f1832..b791bdd633 100644
--- a/fftools/textformat/avtextwriters.h
+++ b/fftools/textformat/avtextwriters.h
@@ -41,7 +41,7 @@ typedef struct AVTextWriter {
     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, ...);
+    void (*writer_vprintf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
 } AVTextWriter;
 
 typedef struct AVTextWriterContext {
diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index d4ac296a42..e52888239e 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -28,23 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 
 /* Compact output */
@@ -157,9 +141,12 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
+
     compact->terminate_line[wctx->level] = 1;
     compact->has_nested_elems[wctx->level] = 0;
 
@@ -210,8 +197,11 @@ static void compact_print_section_header(AVTextFormatContext *wctx, const void *
 
 static void compact_print_section_footer(AVTextFormatContext *wctx)
 {
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
     CompactContext *compact = wctx->priv;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (!compact->nested_section[wctx->level] &&
         compact->terminate_line[wctx->level] &&
diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index ad97173b0b..019bda9d44 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -27,21 +27,7 @@
 #include "avtextformat.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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -80,9 +66,11 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(&wctx->section_pbuf[wctx->level]);
     if (parent_section &&
@@ -104,7 +92,8 @@ static void default_print_section_header(AVTextFormatContext *wctx, const void *
 static void default_print_section_footer(AVTextFormatContext *wctx)
 {
     DefaultContext *def = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
     char buf[32];
 
     if (!section)
diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c
index f692971bcc..d5517f109b 100644
--- a/fftools/textformat/tf_flat.c
+++ b/fftools/textformat/tf_flat.c
@@ -28,22 +28,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
-
+#include "tf_internal.h"
 
 /* Flat output */
 
@@ -118,9 +103,11 @@ static void flat_print_section_header(AVTextFormatContext *wctx, const void *dat
 {
     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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     /* build section header */
     av_bprint_clear(buf);
diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index dd77d0e8bf..8959785295 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -28,21 +28,7 @@
 
 #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                    \
-}
+#include "tf_internal.h"
 
 /* Default output */
 
@@ -104,9 +90,11 @@ 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_clear(buf);
     if (!parent_section) {
diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
new file mode 100644
index 0000000000..362a4cbc38
--- /dev/null
+++ b/fftools/textformat/tf_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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
+ */
+
+/**
+ * @file
+ * Internal utilities for text formatters.
+ */
+
+#ifndef FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+#define FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
+
+#include "avtextformat.h"
+
+#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                    \
+}
+
+
+/**
+ * Safely validate and access a section at a given level
+ */
+static inline const AVTextFormatSection *tf_get_section(AVTextFormatContext *tfc, int level)
+{
+    if (!tfc || level < 0 || level >= SECTION_MAX_NB_LEVELS || !tfc->section[level]) {
+        if (tfc)
+            av_log(tfc, AV_LOG_ERROR, "Invalid section access at level %d\n", level);
+        return NULL;
+    }
+    return tfc->section[level];
+}
+
+/**
+ * Safely access the parent section
+ */
+static inline const AVTextFormatSection *tf_get_parent_section(AVTextFormatContext *tfc, int level)
+{
+    if (level <= 0)
+        return NULL;
+
+    return tf_get_section(tfc, level - 1);
+}
+
+static inline void writer_w8(AVTextFormatContext *wctx, int b)
+{
+    wctx->writer->writer->writer_w8(wctx->writer, b);
+}
+
+static inline void writer_put_str(AVTextFormatContext *wctx, const char *str)
+{
+    wctx->writer->writer->writer_put_str(wctx->writer, str);
+}
+
+static inline void writer_printf(AVTextFormatContext *wctx, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    wctx->writer->writer->writer_vprintf(wctx->writer, fmt, args);
+    va_end(args);
+}
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_INTERNAL_H */
diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c
index 50c3d90440..593d6c2947 100644
--- a/fftools/textformat/tf_json.c
+++ b/fftools/textformat/tf_json.c
@@ -27,22 +27,7 @@
 #include "avtextformat.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                    \
-}
-
+#include "tf_internal.h"
 
 /* JSON output */
 
@@ -103,10 +88,13 @@ static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx
 
 static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
     AVBPrint buf;
-    const AVTextFormatSection *section = wctx->section[wctx->level];
-    const AVTextFormatSection *parent_section = wctx->level ? wctx->section[wctx->level - 1] : NULL;
+
+    if (!section)
+        return;
 
     if (wctx->level && wctx->nb_item[wctx->level - 1])
         writer_put_str(wctx, ",\n");
@@ -141,8 +129,11 @@ static void json_print_section_header(AVTextFormatContext *wctx, const void *dat
 
 static void json_print_section_footer(AVTextFormatContext *wctx)
 {
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         json->indent_level--;
@@ -175,9 +166,8 @@ static inline void json_print_item_str(AVTextFormatContext *wctx,
 
 static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     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);
@@ -188,8 +178,8 @@ static void json_print_str(AVTextFormatContext *wctx, const char *key, const cha
 
 static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
 {
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
     JSONContext *json = wctx->priv;
-    const 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))
@@ -213,4 +203,3 @@ const AVTextFormatter avtextformatter_json = {
     .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
index 28abfc6400..6b09e09ab4 100644
--- a/fftools/textformat/tf_xml.c
+++ b/fftools/textformat/tf_xml.c
@@ -25,21 +25,7 @@
 #include "libavutil/bprint.h"
 #include "libavutil/error.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                    \
-}
+#include "tf_internal.h"
 
 /* XML output */
 
@@ -90,9 +76,11 @@ static av_cold int xml_init(AVTextFormatContext *wctx)
 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;
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
@@ -138,7 +126,10 @@ static void xml_print_section_header(AVTextFormatContext *wctx, const void *data
 static void xml_print_section_footer(AVTextFormatContext *wctx)
 {
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     if (wctx->level == 0) {
         writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
@@ -158,7 +149,10 @@ static void xml_print_value(AVTextFormatContext *wctx, const char *key,
 {
     AVBPrint buf;
     XMLContext *xml = wctx->priv;
-    const struct AVTextFormatSection *section = wctx->section[wctx->level];
+    const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+
+    if (!section)
+        return;
 
     av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
 
@@ -216,4 +210,3 @@ const AVTextFormatter avtextformatter_xml = {
     .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
index 29889598bb..aeb4b5453e 100644
--- a/fftools/textformat/tw_avio.c
+++ b/fftools/textformat/tw_avio.c
@@ -57,14 +57,11 @@ static void io_put_str(AVTextWriterContext *wctx, const char *str)
     avio_write(ctx->avio_context, (const unsigned char *)str, (int)strlen(str));
 }
 
-static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void io_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     IOWriterContext *ctx = wctx->priv;
-    va_list ap;
 
-    va_start(ap, fmt);
-    avio_vprintf(ctx->avio_context, fmt, ap);
-    va_end(ap);
+    avio_vprintf(ctx->avio_context, fmt, vl);
 }
 
 
@@ -73,7 +70,7 @@ const AVTextWriter avtextwriter_avio = {
     .priv_size            = sizeof(IOWriterContext),
     .uninit               = iowriter_uninit,
     .writer_put_str       = io_put_str,
-    .writer_printf        = io_printf,
+    .writer_vprintf       = io_vprintf,
     .writer_w8            = io_w8
 };
 
diff --git a/fftools/textformat/tw_buffer.c b/fftools/textformat/tw_buffer.c
index f8b38414a6..f6e63445d9 100644
--- a/fftools/textformat/tw_buffer.c
+++ b/fftools/textformat/tw_buffer.c
@@ -56,14 +56,11 @@ static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
     av_bprintf(ctx->buffer, "%s", str);
 }
 
-static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static void buffer_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
     BufferWriterContext *ctx = wctx->priv;
 
-    va_list vargs;
-    va_start(vargs, fmt);
-    av_vbprintf(ctx->buffer, fmt, vargs);
-    va_end(vargs);
+    av_vbprintf(ctx->buffer, fmt, vl);
 }
 
 
@@ -72,7 +69,7 @@ const AVTextWriter avtextwriter_buffer = {
     .priv_size            = sizeof(BufferWriterContext),
     .priv_class           = &bufferwriter_class,
     .writer_put_str       = buffer_put_str,
-    .writer_printf        = buffer_printf,
+    .writer_vprintf       = buffer_vprintf,
     .writer_w8            = buffer_w8
 };
 
diff --git a/fftools/textformat/tw_stdout.c b/fftools/textformat/tw_stdout.c
index 23de6f671f..3e2a8dd0d4 100644
--- a/fftools/textformat/tw_stdout.c
+++ b/fftools/textformat/tw_stdout.c
@@ -53,13 +53,9 @@ static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
     printf("%s", str);
 }
 
-static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
+static inline void stdout_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
 {
-    va_list ap;
-
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
+    vprintf(fmt, vl);
 }
 
 
@@ -68,7 +64,7 @@ static const AVTextWriter avtextwriter_stdout = {
     .priv_size            = sizeof(StdOutWriterContext),
     .priv_class           = &stdoutwriter_class,
     .writer_put_str       = stdout_put_str,
-    .writer_printf        = stdout_printf,
+    .writer_vprintf       = stdout_vprintf,
     .writer_w8            = stdout_w8
 };
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 06/13] fftools/tf_internal: Use av_default_item_name
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (4 preceding siblings ...)
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 05/13] fftools/textformat: Introduce common header and deduplicate code softworkz
@ 2025-04-25 23:31             ` softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 07/13] fftools/textformat: Add function avtext_print_integer_flags() softworkz
                               ` (6 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Reviewed-by: Stefano Sabatini <stefasab@gmail.com>
Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/tf_internal.h | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/fftools/textformat/tf_internal.h b/fftools/textformat/tf_internal.h
index 362a4cbc38..484886b7a7 100644
--- a/fftools/textformat/tf_internal.h
+++ b/fftools/textformat/tf_internal.h
@@ -29,13 +29,9 @@
 #include "avtextformat.h"
 
 #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,                  \
+    .item_name  = av_default_item_name,             \
     .option     = name##_options                    \
 }
 
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 07/13] fftools/textformat: Add function avtext_print_integer_flags()
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (5 preceding siblings ...)
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 06/13] fftools/tf_internal: Use av_default_item_name softworkz
@ 2025-04-25 23:31             ` softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 08/13] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
                               ` (5 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

This function works analog to the avtext_print_string() which already
has a flags parameter.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/textformat/avtextformat.c | 25 +++++++++++++++++++++----
 fftools/textformat/avtextformat.h |  2 ++
 2 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index b8e283d00c..86220dc676 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -301,6 +301,21 @@ void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t va
     }
 }
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags)
+{
+    av_assert0(tctx);
+
+    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER)
+        return;
+
+    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
+        && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
+        && !(tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS))
+        return;
+
+    avtext_print_integer(tctx, key, val);
+}
+
 static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src)
 {
     const uint8_t *p, *endp, *srcp = (const uint8_t *)src;
@@ -442,10 +457,12 @@ int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *
 
     section = tctx->section[tctx->level];
 
-    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)))
+    if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER)
+        return 0;
+
+    if (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)) {
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index 071149c5be..f4175c6f48 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -141,6 +141,8 @@ void avtext_print_section_footer(AVTextFormatContext *tctx);
 
 void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
 
+void avtext_print_integer_flags(AVTextFormatContext *tctx, const char *key, int64_t val, int flags);
+
 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);
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 08/13] fftools/ffmpeg_filter: Move some declaration to new header file
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (6 preceding siblings ...)
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 07/13] fftools/textformat: Add function avtext_print_integer_flags() softworkz
@ 2025-04-25 23:31             ` softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 09/13] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
                               ` (4 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

to allow filtergraph printing to access the information.

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_filter.c | 190 +-------------------------------
 fftools/ffmpeg_filter.h | 234 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+), 189 deletions(-)
 create mode 100644 fftools/ffmpeg_filter.h

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index d314aec206..eab9487f97 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "ffmpeg.h"
+#include "ffmpeg_filter.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -42,44 +43,6 @@
 // FIXME private header, used for mid_pred()
 #include "libavcodec/mathops.h"
 
-typedef struct FilterGraphPriv {
-    FilterGraph      fg;
-
-    // name used for logging
-    char             log_name[32];
-
-    int              is_simple;
-    // true when the filtergraph contains only meta filters
-    // that do not modify the frame data
-    int              is_meta;
-    // source filters are present in the graph
-    int              have_sources;
-    int              disable_conversions;
-
-    unsigned         nb_outputs_done;
-
-    const char      *graph_desc;
-
-    int              nb_threads;
-
-    // frame for temporarily holding output from the filtergraph
-    AVFrame         *frame;
-    // frame for sending output to the encoder
-    AVFrame         *frame_enc;
-
-    Scheduler       *sch;
-    unsigned         sch_idx;
-} FilterGraphPriv;
-
-static FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
-{
-    return (FilterGraphPriv*)fg;
-}
-
-static const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
-{
-    return (const FilterGraphPriv*)fg;
-}
 
 // data that is local to the filter thread and not visible outside of it
 typedef struct FilterGraphThread {
@@ -102,157 +65,6 @@ typedef struct FilterGraphThread {
     uint8_t         *eof_out;
 } FilterGraphThread;
 
-typedef struct InputFilterPriv {
-    InputFilter         ifilter;
-
-    InputFilterOptions  opts;
-
-    int                 index;
-
-    AVFilterContext    *filter;
-
-    // used to hold submitted input
-    AVFrame            *frame;
-
-    /* for filters that are not yet bound to an input stream,
-     * this stores the input linklabel, if any */
-    uint8_t            *linklabel;
-
-    // filter data type
-    enum AVMediaType    type;
-    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
-    // same as type otherwise
-    enum AVMediaType    type_src;
-
-    int                 eof;
-    int                 bound;
-    int                 drop_warned;
-    uint64_t            nb_dropped;
-
-    // parameters configured for this input
-    int                 format;
-
-    int                 width, height;
-    AVRational          sample_aspect_ratio;
-    enum AVColorSpace   color_space;
-    enum AVColorRange   color_range;
-
-    int                 sample_rate;
-    AVChannelLayout     ch_layout;
-
-    AVRational          time_base;
-
-    AVFrameSideData   **side_data;
-    int                 nb_side_data;
-
-    AVFifo             *frame_queue;
-
-    AVBufferRef        *hw_frames_ctx;
-
-    int                 displaymatrix_present;
-    int                 displaymatrix_applied;
-    int32_t             displaymatrix[9];
-
-    int                 downmixinfo_present;
-    AVDownmixInfo       downmixinfo;
-
-    struct {
-        AVFrame *frame;
-
-        int64_t last_pts;
-        int64_t end_pts;
-
-        /// marks if sub2video_update should force an initialization
-        unsigned int initialize;
-    } sub2video;
-} InputFilterPriv;
-
-static InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
-{
-    return (InputFilterPriv*)ifilter;
-}
-
-typedef struct FPSConvContext {
-    AVFrame          *last_frame;
-    /* number of frames emitted by the video-encoding sync code */
-    int64_t           frame_number;
-    /* history of nb_frames_prev, i.e. the number of times the
-     * previous frame was duplicated by vsync code in recent
-     * do_video_out() calls */
-    int64_t           frames_prev_hist[3];
-
-    uint64_t          dup_warning;
-
-    int               last_dropped;
-    int               dropped_keyframe;
-
-    enum VideoSyncMethod vsync_method;
-
-    AVRational        framerate;
-    AVRational        framerate_max;
-    const AVRational *framerate_supported;
-    int               framerate_clip;
-} FPSConvContext;
-
-typedef struct OutputFilterPriv {
-    OutputFilter            ofilter;
-
-    int                     index;
-
-    void                   *log_parent;
-    char                    log_name[32];
-
-    char                   *name;
-
-    AVFilterContext        *filter;
-
-    /* desired output stream properties */
-    int                     format;
-    int                     width, height;
-    int                     sample_rate;
-    AVChannelLayout         ch_layout;
-    enum AVColorSpace       color_space;
-    enum AVColorRange       color_range;
-
-    AVFrameSideData       **side_data;
-    int                     nb_side_data;
-
-    // time base in which the output is sent to our downstream
-    // does not need to match the filtersink's timebase
-    AVRational              tb_out;
-    // at least one frame with the above timebase was sent
-    // to our downstream, so it cannot change anymore
-    int                     tb_out_locked;
-
-    AVRational              sample_aspect_ratio;
-
-    AVDictionary           *sws_opts;
-    AVDictionary           *swr_opts;
-
-    // those are only set if no format is specified and the encoder gives us multiple options
-    // They point directly to the relevant lists of the encoder.
-    const int              *formats;
-    const AVChannelLayout  *ch_layouts;
-    const int              *sample_rates;
-    const enum AVColorSpace *color_spaces;
-    const enum AVColorRange *color_ranges;
-
-    AVRational              enc_timebase;
-    int64_t                 trim_start_us;
-    int64_t                 trim_duration_us;
-    // offset for output timestamps, in AV_TIME_BASE_Q
-    int64_t                 ts_offset;
-    int64_t                 next_pts;
-    FPSConvContext          fps;
-
-    unsigned                flags;
-} OutputFilterPriv;
-
-static OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
-{
-    return (OutputFilterPriv*)ofilter;
-}
-
 typedef struct FilterCommand {
     char *target;
     char *command;
diff --git a/fftools/ffmpeg_filter.h b/fftools/ffmpeg_filter.h
new file mode 100644
index 0000000000..94b94beece
--- /dev/null
+++ b/fftools/ffmpeg_filter.h
@@ -0,0 +1,234 @@
+/*
+ * 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_FFMPEG_FILTER_H
+#define FFTOOLS_FFMPEG_FILTER_H
+
+#include "ffmpeg.h"
+
+#include <stdint.h>
+
+#include "ffmpeg_sched.h"
+#include "sync_queue.h"
+
+#include "libavfilter/avfilter.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/dict.h"
+#include "libavutil/fifo.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/rational.h"
+#include "libavutil/bprint.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/downmix_info.h"
+
+typedef struct FilterGraphPriv {
+    FilterGraph      fg;
+
+    // name used for logging
+    char             log_name[32];
+
+    int              is_simple;
+    // true when the filtergraph contains only meta filters
+    // that do not modify the frame data
+    int              is_meta;
+    // source filters are present in the graph
+    int              have_sources;
+    int              disable_conversions;
+
+    unsigned         nb_outputs_done;
+
+    const char      *graph_desc;
+
+    int              nb_threads;
+
+    // frame for temporarily holding output from the filtergraph
+    AVFrame         *frame;
+    // frame for sending output to the encoder
+    AVFrame         *frame_enc;
+
+    Scheduler       *sch;
+    unsigned         sch_idx;
+
+    AVBPrint graph_print_buf;
+
+} FilterGraphPriv;
+
+static inline FilterGraphPriv *fgp_from_fg(FilterGraph *fg)
+{
+    return (FilterGraphPriv*)fg;
+}
+
+static inline const FilterGraphPriv *cfgp_from_cfg(const FilterGraph *fg)
+{
+    return (const FilterGraphPriv*)fg;
+}
+
+typedef struct InputFilterPriv {
+    InputFilter         ifilter;
+
+    InputFilterOptions  opts;
+
+    int                 index;
+
+    AVFilterContext    *filter;
+
+    // used to hold submitted input
+    AVFrame            *frame;
+
+    /* for filters that are not yet bound to an input stream,
+     * this stores the input linklabel, if any */
+    uint8_t            *linklabel;
+
+    // filter data type
+    enum AVMediaType    type;
+    // source data type: AVMEDIA_TYPE_SUBTITLE for sub2video,
+    // same as type otherwise
+    enum AVMediaType    type_src;
+
+    int                 eof;
+    int                 bound;
+    int                 drop_warned;
+    uint64_t            nb_dropped;
+
+    // parameters configured for this input
+    int                 format;
+
+    int                 width, height;
+    AVRational          sample_aspect_ratio;
+    enum AVColorSpace   color_space;
+    enum AVColorRange   color_range;
+
+    int                 sample_rate;
+    AVChannelLayout     ch_layout;
+
+    AVRational          time_base;
+
+    AVFrameSideData   **side_data;
+    int                 nb_side_data;
+
+    AVFifo             *frame_queue;
+
+    AVBufferRef        *hw_frames_ctx;
+
+    int                 displaymatrix_present;
+    int                 displaymatrix_applied;
+    int32_t             displaymatrix[9];
+
+    int                 downmixinfo_present;
+    AVDownmixInfo       downmixinfo;
+
+    struct {
+        AVFrame *frame;
+
+        int64_t last_pts;
+        int64_t end_pts;
+
+        /// marks if sub2video_update should force an initialization
+        unsigned int initialize;
+    } sub2video;
+} InputFilterPriv;
+
+static inline InputFilterPriv *ifp_from_ifilter(InputFilter *ifilter)
+{
+    return (InputFilterPriv*)ifilter;
+}
+
+typedef struct FPSConvContext {
+    AVFrame          *last_frame;
+    /* number of frames emitted by the video-encoding sync code */
+    int64_t           frame_number;
+    /* history of nb_frames_prev, i.e. the number of times the
+     * previous frame was duplicated by vsync code in recent
+     * do_video_out() calls */
+    int64_t           frames_prev_hist[3];
+
+    uint64_t          dup_warning;
+
+    int               last_dropped;
+    int               dropped_keyframe;
+
+    enum VideoSyncMethod vsync_method;
+
+    AVRational        framerate;
+    AVRational        framerate_max;
+    const AVRational *framerate_supported;
+    int               framerate_clip;
+} FPSConvContext;
+
+
+typedef struct OutputFilterPriv {
+    OutputFilter            ofilter;
+
+    int                     index;
+
+    void                   *log_parent;
+    char                    log_name[32];
+
+    char                   *name;
+
+    AVFilterContext        *filter;
+
+    /* desired output stream properties */
+    int                     format;
+    int                     width, height;
+    int                     sample_rate;
+    AVChannelLayout         ch_layout;
+    enum AVColorSpace       color_space;
+    enum AVColorRange       color_range;
+
+    AVFrameSideData       **side_data;
+    int                     nb_side_data;
+
+    // time base in which the output is sent to our downstream
+    // does not need to match the filtersink's timebase
+    AVRational              tb_out;
+    // at least one frame with the above timebase was sent
+    // to our downstream, so it cannot change anymore
+    int                     tb_out_locked;
+
+    AVRational              sample_aspect_ratio;
+
+    AVDictionary           *sws_opts;
+    AVDictionary           *swr_opts;
+
+    // those are only set if no format is specified and the encoder gives us multiple options
+    // They point directly to the relevant lists of the encoder.
+    const int              *formats;
+    const AVChannelLayout  *ch_layouts;
+    const int              *sample_rates;
+    const enum AVColorSpace *color_spaces;
+    const enum AVColorRange *color_ranges;
+
+    AVRational              enc_timebase;
+    int64_t                 trim_start_us;
+    int64_t                 trim_duration_us;
+    // offset for output timestamps, in AV_TIME_BASE_Q
+    int64_t                 ts_offset;
+    int64_t                 next_pts;
+    FPSConvContext          fps;
+
+    unsigned                flags;
+} OutputFilterPriv;
+
+static inline OutputFilterPriv *ofp_from_ofilter(OutputFilter *ofilter)
+{
+    return (OutputFilterPriv*)ofilter;
+}
+
+#endif /* FFTOOLS_FFMPEG_FILTER_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 09/13] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx()
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (7 preceding siblings ...)
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 08/13] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
@ 2025-04-25 23:31             ` softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 10/13] fftools/resources: Add resource manager files softworkz
                               ` (3 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/APIchanges         |  3 +++
 libavfilter/avfilter.c |  9 +++++++++
 libavfilter/avfilter.h | 12 ++++++++++++
 3 files changed, 24 insertions(+)

diff --git a/doc/APIchanges b/doc/APIchanges
index 75d66f87f3..d0869561f3 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2025-02-xx - xxxxxxxxxx - lavfi 10.10.100 - avfilter.h
+  Add avfilter_link_get_hw_frames_ctx().
+
 2025-04-21 - xxxxxxxxxx - lavu 60.2.100 - log.h
   Add AV_CLASS_CATEGORY_HWDEVICE.
 
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index 64c1075c40..c76d43a215 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -989,6 +989,15 @@ enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx)
     return pads[pad_idx].type;
 }
 
+AVBufferRef *avfilter_link_get_hw_frames_ctx(AVFilterLink *link)
+{
+    FilterLink *plink = ff_filter_link(link);
+    if (plink->hw_frames_ctx)
+        return av_buffer_ref(plink->hw_frames_ctx);
+
+    return NULL;
+}
+
 static int default_filter_frame(AVFilterLink *link, AVFrame *frame)
 {
     return ff_filter_frame(link->dst->outputs[0], frame);
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index a89d3cf658..f85929dc5c 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -96,6 +96,18 @@ const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
  */
 enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
 
+/**
+ * Get the hardware frames context of a filter link.
+ *
+ * @param link an AVFilterLink
+ *
+ * @return a ref-counted copy of the link's hw_frames_ctx field if there is
+ *         a hardware frames context associated with the link or NULL otherwise.
+ *         The returned AVBufferRef needs to be released with av_buffer_unref()
+ *         when it is no longer used.
+ */
+AVBufferRef* avfilter_link_get_hw_frames_ctx(AVFilterLink *link);
+
 /**
  * Lists of formats / etc. supported by an end of a link.
  *
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 10/13] fftools/resources: Add resource manager files
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (8 preceding siblings ...)
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 09/13] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
@ 2025-04-25 23:31             ` softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 11/13] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
                               ` (2 subsequent siblings)
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 ffbuild/common.mak           |  28 ++-
 fftools/Makefile             |   3 +-
 fftools/resources/.gitignore |   4 +
 fftools/resources/Makefile   |  27 +++
 fftools/resources/graph.css  | 353 +++++++++++++++++++++++++++++++++++
 fftools/resources/graph.html |  86 +++++++++
 fftools/resources/resman.c   | 213 +++++++++++++++++++++
 fftools/resources/resman.h   |  50 +++++
 8 files changed, 762 insertions(+), 2 deletions(-)
 create mode 100644 fftools/resources/.gitignore
 create mode 100644 fftools/resources/Makefile
 create mode 100644 fftools/resources/graph.css
 create mode 100644 fftools/resources/graph.html
 create mode 100644 fftools/resources/resman.c
 create mode 100644 fftools/resources/resman.h

diff --git a/ffbuild/common.mak b/ffbuild/common.mak
index ca45a0f368..6717092d44 100644
--- a/ffbuild/common.mak
+++ b/ffbuild/common.mak
@@ -139,6 +139,32 @@ else
 	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
 endif
 
+# 1) Preprocess CSS to a minified version
+%.css.min: %.css
+	# Must start with a tab in the real Makefile
+	sed 's!/\\*.*\\*/!!g' $< \
+	| tr '\n' ' ' \
+	| tr -s ' ' \
+	| sed 's/^ //; s/ $$//' \
+	> $@
+
+# 2) Gzip the minified CSS
+%.css.min.gz: %.css.min
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) >$@
+
+# 3) Convert the gzipped CSS to a .c array
+%.css.c: %.css.min.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
+# 4) Gzip the HTML file (no minification needed)
+%.html.gz: TAG = GZIP
+%.html.gz: %.html
+	$(M)gzip -nc9 $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) > $@
+
+# 5) Convert the gzipped HTML to a .c array
+%.html.c: %.html.gz $(BIN2CEXE)
+	$(BIN2C) $(patsubst $(SRC_PATH)/%,$(SRC_LINK)/%,$<) $@ $(subst .,_,$(basename $(notdir $@)))
+
 clean::
 	$(RM) $(BIN2CEXE) $(CLEANSUFFIXES:%=ffbuild/%)
 
@@ -214,7 +240,7 @@ $(TOOLOBJS): | tools
 
 OUTDIRS := $(OUTDIRS) $(dir $(OBJS) $(HOBJS) $(HOSTOBJS) $(SLIBOBJS) $(SHLIBOBJS) $(STLIBOBJS) $(TESTOBJS))
 
-CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
+CLEANSUFFIXES     = *.d *.gcda *.gcno *.h.c *.ho *.map *.o *.objs *.pc *.ptx *.ptx.gz *.ptx.c *.ver *.version *.html.gz *.html.c *.css.gz *.css.c  *$(DEFAULT_X86ASMD).asm *~ *.ilk *.pdb
 LIBSUFFIXES       = *.a *.lib *.so *.so.* *.dylib *.dll *.def *.dll.a
 
 define RULES
diff --git a/fftools/Makefile b/fftools/Makefile
index e9c9891c34..a30bec889e 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -42,7 +42,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -56,6 +56,7 @@ all: $(AVPROGS)
 fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
+OUTDIRS += fftools/resources
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/resources/.gitignore b/fftools/resources/.gitignore
new file mode 100644
index 0000000000..5f496535a6
--- /dev/null
+++ b/fftools/resources/.gitignore
@@ -0,0 +1,4 @@
+*.html.c
+*.css.c
+*.html.gz
+*.css.gz
diff --git a/fftools/resources/Makefile b/fftools/resources/Makefile
new file mode 100644
index 0000000000..2487b1ca52
--- /dev/null
+++ b/fftools/resources/Makefile
@@ -0,0 +1,27 @@
+clean::
+	$(RM) $(CLEANSUFFIXES:%=fftools/resources/%)
+
+
+HTML_RESOURCES := $(SRC_PATH)/fftools/resources/graph.html \
+
+# .html => (gzip) .html.gz => (bin2c) .html.c => (cc) .o
+HTML_RESOURCES_GZ := $(HTML_RESOURCES:.html=.html.gz)
+HTML_RESOURCES_C := $(HTML_RESOURCES_GZ:.html.gz=.html.c)
+HTML_RESOURCES_OBJS := $(HTML_RESOURCES_C:.c=.o)
+
+CSS_RESOURCES := $(SRC_PATH)/fftools/resources/graph.css   \
+
+# .css => (sh) .css.min  => (gzip) .css.min.gz => (bin2c) .css.c => (cc) .o
+CSS_RESOURCES_MIN := $(CSS_RESOURCES:.css=.css.min)
+CSS_RESOURCES_GZ := $(CSS_RESOURCES_MIN:.css.min=.css.min.gz)
+CSS_RESOURCES_C := $(CSS_RESOURCES_GZ:.css.min.gz=.css.c)
+CSS_RESOURCES_OBJS := $(CSS_RESOURCES_C:.c=.o)
+
+# Uncomment to prevent deletion
+#.PRECIOUS: %.css.c %.css.min %.css.gz %.css.min.gz %.html.gz %.html.c
+
+OBJS-resman +=                  \
+    fftools/resources/resman.o          \
+    $(HTML_RESOURCES_OBJS)      \
+    $(CSS_RESOURCES_OBJS)       \
+
diff --git a/fftools/resources/graph.css b/fftools/resources/graph.css
new file mode 100644
index 0000000000..ab480673ab
--- /dev/null
+++ b/fftools/resources/graph.css
@@ -0,0 +1,353 @@
+/* Variables */
+.root {
+    --ff-colvideo: #6eaa7b;
+    --ff-colaudio: #477fb3;
+    --ff-colsubtitle: #ad76ab;
+    --ff-coltext: #666;
+}
+
+/* Common & Misc */
+.ff-inputfiles rect, .ff-outputfiles rect, .ff-inputstreams rect, .ff-outputstreams rect, .ff-decoders rect, .ff-encoders rect {
+    stroke-width: 0;
+    stroke: transparent;
+    filter: none !important;
+    fill: transparent !important;
+    display: none !important;
+}
+
+.cluster span {
+    color: var(--ff-coltext);
+}
+
+.cluster rect {
+    stroke: #dfdfdf !important;
+    transform: translateY(-2.3rem);
+    filter: drop-shadow(1px 2px 2px rgba(185,185,185,0.2)) !important;
+    rx: 8;
+    ry: 8;
+}
+
+.cluster-label {
+    font-size: 1.1rem;
+}
+
+    .cluster-label .nodeLabel {
+        display: block;
+        font-weight: 500;
+        color: var(--ff-coltext);
+    }
+
+    .cluster-label div {
+        max-width: unset !important;
+        padding: 3px;
+    }
+
+    .cluster-label foreignObject {
+        transform: translateY(-0.7rem);
+    }
+
+/* Input and output files */
+.node.ff-inputfile .label foreignObject, .node.ff-outputfile .label foreignObject {
+    overflow: visible;
+}
+
+.cluster.ff-inputfile .cluster-label foreignObject div:not(foreignObject div div), .cluster.ff-outputfile .cluster-label foreignObject div:not(foreignObject div div) {
+    display: table !important;
+}
+
+.nodeLabel div.ff-inputfile, .nodeLabel div.ff-outputfile {
+    font-size: 1.1rem;
+    font-weight: 500;
+    min-width: 14rem;
+    width: 100%;
+    display: flex;
+    color: var(--ff-coltext);
+    margin-top: 0.1rem;
+    line-height: 1.35;
+    padding-bottom: 1.9rem;
+}
+
+.nodeLabel div.ff-outputfile {
+    flex-direction: row-reverse;
+}
+
+.ff-inputfile .index, .ff-outputfile .index {
+    order: 2;
+    color: var(--ff-coltext);
+    text-align: center;
+    border-radius: 0.45rem;
+    border: 0.18em solid #666666db;
+    font-weight: 600;
+    padding: 0 0.3em;
+    opacity: 0.8;
+}
+
+    .ff-inputfile .index::before {
+        content: 'In ';
+    }
+
+    .ff-outputfile .index::before {
+        content: 'Out ';
+    }
+
+.ff-inputfile .demuxer_name, .ff-outputfile .muxer_name {
+    flex: 1;
+    order: 1;
+    font-size: 0.9rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: center;
+    max-width: 8rem;
+    align-content: center;
+    margin: 0.2rem 0.4rem 0 0.4rem;
+}
+
+.ff-inputfile .file_extension, .ff-outputfile .file_extension {
+    order: 0;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.45rem;
+    font-weight: 600;
+    padding: 0 0.4em;
+    align-content: center;
+    opacity: 0.8;
+}
+
+.ff-inputfile .url, .ff-outputfile .url {
+    order: 4;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0.75rem;
+    font-size: 0.7rem;
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin: 0 0.3rem;
+    direction: rtl;
+    color: #999;
+}
+
+.cluster.ff-inputfile rect, .cluster.ff-outputfile rect {
+    transform: translateY(-1.8rem);
+    fill: url(#ff-radgradient);
+}
+
+/* Input and output streams */
+.node.ff-inputstream rect, .node.ff-outputstream rect {
+    padding: 0 !important;
+    margin: 0 !important;
+    border: none !important;
+    fill: white;
+    stroke: #e5e5e5 !important;
+    height: 2.7rem;
+    transform: translateY(0.2rem);
+    filter: none;
+    rx: 3;
+    ry: 3;
+}
+
+.node.ff-inputstream .label foreignObject, .node.ff-outputstream .label foreignObject {
+    transform: translateY(-0.2%);
+    overflow: visible;
+}
+
+    .node.ff-inputstream .label foreignObject div:not(foreignObject div div), .node.ff-outputstream .label foreignObject div:not(foreignObject div div) {
+        display: block !important;
+        line-height: 1.5 !important;
+    }
+
+.nodeLabel div.ff-inputstream, .nodeLabel div.ff-outputstream {
+    font-size: 1.0rem;
+    font-weight: 500;
+    min-width: 12rem;
+    width: 100%;
+    display: flex;
+}
+
+.nodeLabel div.ff-outputstream {
+    flex-direction: row-reverse;
+}
+
+.ff-inputstream .name, .ff-outputstream .name {
+    flex: 1;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-align: left;
+    align-content: center;
+    margin-bottom: -0.15rem;
+}
+
+.ff-inputstream .index, .ff-outputstream .index {
+    flex: 0 0 1.4rem;
+    background-color: #888;
+    color: white;
+    text-align: center;
+    border-radius: 0.3rem;
+    font-weight: 600;
+    margin-right: -0.3rem;
+    margin-left: 0.4rem;
+    opacity: 0.8;
+}
+
+.ff-outputstream .index {
+    margin-right: 0.6rem;
+    margin-left: -0.4rem;
+}
+
+.ff-inputstream::before, .ff-outputstream::before {
+    font-variant-emoji: text;
+    flex: 0 0 2rem;
+    margin-left: -0.8rem;
+    margin-right: 0.2rem;
+}
+
+.ff-outputstream::before {
+    margin-left: 0.2rem;
+    margin-right: -0.6rem;
+}
+
+.ff-inputstream.video::before, .ff-outputstream.video::before {
+    content: '\239A';
+    color: var(--ff-colvideo);
+    font-size: 2.25rem;
+    line-height: 0.5;
+    font-weight: bold;
+}
+
+.ff-inputstream.audio::before, .ff-outputstream.audio::before {
+    content: '\1F39D';
+    color: var(--ff-colaudio);
+    font-size: 1.75rem;
+    line-height: 0.9;
+}
+
+.ff-inputstream.subtitle::before, .ff-outputstream.subtitle::before {
+    content: '\1AC';
+    color: var(--ff-colsubtitle);
+    font-size: 1.2rem;
+    line-height: 1.1;
+    transform: scaleX(1.5);
+    margin-top: 0.050rem;
+}
+
+.ff-inputstream.attachment::before, .ff-outputstream.attachment::before {
+    content: '\1F4CE';
+    font-size: 1.3rem;
+    line-height: 1.15;
+}
+
+.ff-inputstream.data::before, .ff-outputstream.data::before {
+    content: '\27E8\2219\2219\2219\27E9';
+    font-size: 1.15rem;
+    line-height: 1.17;
+    letter-spacing: -0.3px;
+}
+
+/* Filter Graphs */
+.cluster.ff-filters rect {
+    stroke-dasharray: 6 !important;
+    stroke-width: 1.3px;
+    stroke: #d1d1d1 !important;
+    filter: none !important;
+}
+
+.cluster.ff-filters div.ff-filters .id {
+    display: none;
+}
+
+.cluster.ff-filters div.ff-filters .name {
+    margin-right: 0.5rem;
+    font-size: 0.9rem;
+}
+
+.cluster.ff-filters div.ff-filters .description {
+    font-weight: 400;
+    font-size: 0.75rem;
+    vertical-align: middle;
+    color: #777;
+    font-family: Cascadia Code, Lucida Console, monospace;
+}
+
+/* Filter Shapes */
+.node.ff-filter rect {
+    rx: 10;
+    ry: 10;
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.node.ff-filter .label foreignObject {
+    transform: translateY(-0.4rem);
+    overflow: visible;
+}
+
+.nodeLabel div.ff-filter {
+    font-size: 1.0rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    min-width: 5.5rem;
+    margin-bottom: 0.5rem;
+}
+
+    .nodeLabel div.ff-filter span {
+        color: inherit;
+    }
+
+/* Decoders & Encoders */
+.node.ff-decoder rect, .node.ff-encoder rect {
+    stroke-width: 1px;
+    stroke: #d3d3d3;
+    fill: url(#ff-filtergradient);
+    filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.1));
+}
+
+.nodeLabel div.ff-decoder, .nodeLabel div.ff-encoder {
+    font-size: 0.85rem;
+    font-weight: 500;
+    min-width: 3.5rem;
+}
+
+/* Links and Arrows */
+path.flowchart-link[id|='video'] {
+    stroke: var(--ff-colvideo);
+}
+
+path.flowchart-link[id|='audio'] {
+    stroke: var(--ff-colaudio);
+}
+
+path.flowchart-link[id|='subtitle'] {
+    stroke: var(--ff-colsubtitle);
+}
+
+marker.marker path {
+    fill: context-stroke;
+}
+
+.edgeLabel foreignObject {
+    transform: translateY(-1rem);
+}
+
+.edgeLabel p {
+    background: transparent;
+    white-space: nowrap;
+    margin: 1rem 0.5rem !important;
+    font-weight: 500;
+    color: var(--ff-coltext);
+}
+
+.edgeLabel, .labelBkg {
+    background: transparent;
+}
+
+.edgeLabels .edgeLabel * {
+    font-size: 0.8rem;
+}
diff --git a/fftools/resources/graph.html b/fftools/resources/graph.html
new file mode 100644
index 0000000000..cd94276fd4
--- /dev/null
+++ b/fftools/resources/graph.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <title>FFmpeg Graph</title>
+</head>
+<body>
+<style>
+    html {
+        color: #666;
+        font-family: Roboto;
+        height: 100%;
+    }
+
+    body {
+        background-color: #f9f9f9;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        margin: 0;
+        padding: 1.7rem 1.7rem 3.5rem 1.7rem;
+    }
+
+    div#banner {
+        align-items: center;
+        display: flex;
+        flex-direction: row;
+        margin-bottom: 1.5rem;
+        margin-left: 0.6vw;
+    }
+
+    div#header {
+        aspect-ratio: 1/1;
+        background-image: url(https://trac.ffmpeg.org/ffmpeg-logo.png);
+        background-size: cover;
+        width: 1.6rem;
+    }
+
+    h1 {
+        font-size: 1.2rem;
+        margin: 0 0.5rem;
+    }
+
+    pre.mermaid {
+        align-items: center;
+        background-color: white;
+        box-shadow: 2px 2px 25px 0px #00000010;
+        color: transparent;
+        display: flex;
+        flex: 1;
+        justify-content: center;
+        margin: 0;
+        overflow: overlay;
+    }
+
+    pre.mermaid svg {
+        height: auto;
+        margin: 0;
+        max-width: unset !important;
+        width: auto;
+    }
+
+    pre.mermaid svg * {
+        user-select: none;
+    }
+</style>
+<div id="banner">
+    <div id="header"></div>
+    <h1>FFmpeg Execution Graph</h1>
+</div>
+<pre class="mermaid">
+__###__
+</pre>
+<script type="module">
+        import vanillaJsWheelZoom from 'https://cdn.jsdelivr.net/npm/vanilla-js-wheel-zoom@9.0.4/+esm';
+        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
+        function initViewer() {
+            var element = document.querySelector('.mermaid svg')
+            vanillaJsWheelZoom.create('pre.mermaid svg', { type: 'html', smoothTimeDrag: 0, width: element.clientWidth, height: element.clientHeight, maxScale: 3 });
+        }
+        mermaid.initialize({ startOnLoad: false }); 
+        document.fonts.ready.then(() => { mermaid.run({ querySelector: '.mermaid', postRenderCallback: initViewer }); });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/fftools/resources/resman.c b/fftools/resources/resman.c
new file mode 100644
index 0000000000..488aaeecf6
--- /dev/null
+++ b/fftools/resources/resman.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <zlib.h>
+#include "resman.h"
+#include <libavformat/url.h>
+#include "fftools/ffmpeg_filter.h"
+#include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+
+extern const unsigned char ff_graph_html_data[];
+extern const unsigned int ff_graph_html_len;
+
+extern const unsigned char ff_graph_css_data[];
+extern const unsigned ff_graph_css_len;
+
+static const FFResourceDefinition resource_definitions[] = {
+    [FF_RESOURCE_GRAPH_CSS]   = { FF_RESOURCE_GRAPH_CSS,   "graph.css",   &ff_graph_css_data[0],   &ff_graph_css_len   },
+    [FF_RESOURCE_GRAPH_HTML]  = { FF_RESOURCE_GRAPH_HTML,  "graph.html",  &ff_graph_html_data[0],  &ff_graph_html_len  },
+};
+
+
+static const AVClass resman_class = {
+    .class_name = "ResourceManager",
+};
+
+typedef struct ResourceManagerContext {
+    const AVClass *class;
+    AVDictionary *resource_dic;
+} ResourceManagerContext;
+
+static AVMutex mutex = AV_MUTEX_INITIALIZER;
+
+ResourceManagerContext *resman_ctx = NULL;
+
+
+static int decompress_gzip(ResourceManagerContext *ctx, uint8_t *in, unsigned in_len, char **out, size_t *out_len)
+{
+    z_stream strm;
+    unsigned chunk = 65534;
+    int ret;
+    uint8_t *buf;
+
+    *out = NULL;
+    memset(&strm, 0, sizeof(strm));
+
+    // Allocate output buffer with extra byte for null termination
+    buf = (uint8_t *)av_mallocz(chunk + 1);
+    if (!buf) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to allocate decompression buffer\n");
+        return AVERROR(ENOMEM);
+    }
+
+    // 15 + 16 tells zlib to detect GZIP or zlib automatically
+    ret = inflateInit2(&strm, 15 + 16);
+    if (ret != Z_OK) {
+        av_log(ctx, AV_LOG_ERROR, "Error during zlib initialization: %s\n", strm.msg);
+        av_free(buf);
+        return AVERROR(ENOSYS);
+    }
+
+    strm.avail_in  = in_len;
+    strm.next_in   = in;
+    strm.avail_out = chunk;
+    strm.next_out  = buf;
+
+    ret = inflate(&strm, Z_FINISH);
+    if (ret != Z_OK && ret != Z_STREAM_END) {
+        av_log(ctx, AV_LOG_ERROR, "Inflate failed: %d, %s\n", ret, strm.msg);
+        inflateEnd(&strm);
+        av_free(buf);
+        return (ret == Z_STREAM_END) ? Z_OK : ((ret == Z_OK) ? Z_BUF_ERROR : ret);
+    }
+
+    if (strm.avail_out == 0) {
+        // TODO: Error or loop decoding?
+        av_log(ctx, AV_LOG_WARNING, "Decompression buffer may be too small\n");
+    }
+
+    *out_len = chunk - strm.avail_out;
+    buf[*out_len] = 0; // Ensure null termination
+
+    inflateEnd(&strm);
+    *out = (char *)buf;
+    return Z_OK;
+}
+
+static ResourceManagerContext *get_resman_context(void)
+{
+    ResourceManagerContext *res = resman_ctx;
+
+    ff_mutex_lock(&mutex);
+
+    if (res)
+        goto end;
+
+    res = av_mallocz(sizeof(ResourceManagerContext));
+    if (!res) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to allocate resource manager context\n");
+        goto end;
+    }
+
+    res->class = &resman_class;
+    resman_ctx = res;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
+
+
+void ff_resman_uninit(void)
+{
+    ff_mutex_lock(&mutex);
+
+    if (resman_ctx) {
+        if (resman_ctx->resource_dic)
+            av_dict_free(&resman_ctx->resource_dic);
+        av_freep(&resman_ctx);
+    }
+
+    ff_mutex_unlock(&mutex);
+}
+
+
+char *ff_resman_get_string(FFResourceId resource_id)
+{
+    ResourceManagerContext *ctx               = get_resman_context();
+    FFResourceDefinition resource_definition = { 0 };
+    AVDictionaryEntry *dic_entry;
+    char *res = NULL;
+
+    if (!ctx)
+        return NULL;
+
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(resource_definitions); ++i) {
+        FFResourceDefinition def = resource_definitions[i];
+        if (def.resource_id == resource_id) {
+            resource_definition = def;
+            break;
+        }
+    }
+
+    if (!resource_definition.name) {
+        av_log(ctx, AV_LOG_ERROR, "Unable to find resource with ID %d\n", resource_id);
+        return NULL;
+    }
+
+    ff_mutex_lock(&mutex);
+
+    dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+    if (!dic_entry) {
+        char *out = NULL;
+        size_t out_len;
+        int dict_ret;
+
+        int ret = decompress_gzip(ctx, (uint8_t *)resource_definition.data, *resource_definition.data_len, &out, &out_len);
+
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Unable to decompress the resource with ID %d\n", resource_id);
+            goto end;
+        }
+
+        dict_ret = av_dict_set(&ctx->resource_dic, resource_definition.name, out, 0);
+        if (dict_ret < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to store decompressed resource in dictionary: %d\n", dict_ret);
+            av_freep(&out);
+            goto end;
+        }
+
+        av_freep(&out);
+        dic_entry = av_dict_get(ctx->resource_dic, resource_definition.name, NULL, 0);
+
+        if (!dic_entry) {
+            av_log(NULL, AV_LOG_ERROR, "Failed to retrieve resource from dictionary after storing it\n");
+            goto end;
+        }
+    }
+
+    res = dic_entry->value;
+
+end:
+    ff_mutex_unlock(&mutex);
+    return res;
+}
diff --git a/fftools/resources/resman.h b/fftools/resources/resman.h
new file mode 100644
index 0000000000..6485db5091
--- /dev/null
+++ b/fftools/resources/resman.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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_RESOURCES_RESMAN_H
+#define FFTOOLS_RESOURCES_RESMAN_H
+
+#include <stdint.h>
+
+#include "config.h"
+#include "fftools/ffmpeg.h"
+#include "libavutil/avutil.h"
+#include "libavutil/bprint.h"
+#include "fftools/textformat/avtextformat.h"
+
+typedef enum {
+    FF_RESOURCE_GRAPH_CSS,
+    FF_RESOURCE_GRAPH_HTML,
+} FFResourceId;
+
+typedef struct FFResourceDefinition {
+    FFResourceId resource_id;
+    const char *name;
+
+    const unsigned char *data;
+    const unsigned *data_len;
+
+} FFResourceDefinition;
+
+void ff_resman_uninit(void);
+
+char *ff_resman_get_string(FFResourceId resource_id);
+
+#endif /* FFTOOLS_RESOURCES_RESMAN_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 11/13] fftools/ffmpeg_mux: Make ms_from_ost() inline
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (9 preceding siblings ...)
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 10/13] fftools/resources: Add resource manager files softworkz
@ 2025-04-25 23:31             ` softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 12/13] fftools/graphprint: Add execution graph printing softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 13/13] fftools/graphprint: Now, make it a Killer-Feature! softworkz
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 fftools/ffmpeg_mux.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fftools/ffmpeg_mux.h b/fftools/ffmpeg_mux.h
index f41f2c18fa..4ca8ab73a4 100644
--- a/fftools/ffmpeg_mux.h
+++ b/fftools/ffmpeg_mux.h
@@ -123,7 +123,7 @@ typedef struct Muxer {
 
 int mux_check_init(void *arg);
 
-static MuxStream *ms_from_ost(OutputStream *ost)
+static inline MuxStream *ms_from_ost(OutputStream *ost)
 {
     return (MuxStream*)ost;
 }
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 12/13] fftools/graphprint: Add execution graph printing
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (10 preceding siblings ...)
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 11/13] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
@ 2025-04-25 23:31             ` softworkz
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 13/13] fftools/graphprint: Now, make it a Killer-Feature! softworkz
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

The key benefits are:

- Different to other graph printing methods, this is outputting:
  - all graphs with runtime state
    (including auto-inserted filters)
  - each graph with its inputs and outputs
  - all filters with their in- and output pads
  - all connections between all input- and output pads
  - for each connection:
    - the runtime-negotiated format and media type
    - the hw context
    - if video hw context, both: hw pixfmt + sw pixfmt
- Output can either be printed to stdout or written to specified file
- Output is machine-readable
- Use the same output implementation as ffprobe, supporting multiple
  formats

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi                   |   10 +
 fftools/Makefile                  |   20 +-
 fftools/ffmpeg.c                  |    4 +
 fftools/ffmpeg.h                  |    3 +
 fftools/ffmpeg_filter.c           |    5 +
 fftools/ffmpeg_opt.c              |   13 +
 fftools/graph/graphprint.c        | 1101 +++++++++++++++++++++++++++++
 fftools/graph/graphprint.h        |   30 +
 fftools/textformat/avtextformat.c |    2 +
 fftools/textformat/avtextformat.h |   29 +
 fftools/textformat/tf_mermaid.c   |  658 +++++++++++++++++
 fftools/textformat/tf_mermaid.h   |   41 ++
 12 files changed, 1915 insertions(+), 1 deletion(-)
 create mode 100644 fftools/graph/graphprint.c
 create mode 100644 fftools/graph/graphprint.h
 create mode 100644 fftools/textformat/tf_mermaid.c
 create mode 100644 fftools/textformat/tf_mermaid.h

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 17ba876ea3..35675b5309 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1394,6 +1394,16 @@ It is on by default, to explicitly disable it you need to specify @code{-nostats
 @item -stats_period @var{time} (@emph{global})
 Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
 
+@item -print_graphs (@emph{global})
+Prints execution graph details to stderr in the format set via -print_graphs_format.
+
+@item -print_graphs_file @var{filename} (@emph{global})
+Writes execution graph details to the specified file in the format set via -print_graphs_format.
+
+@item -print_graphs_format @var{format} (@emph{global})
+Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
+The default format is json.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index a30bec889e..361a4fd574 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -9,6 +9,8 @@ AVBASENAMES  = ffmpeg ffplay ffprobe
 ALLAVPROGS   = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
 ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
 
+include $(SRC_PATH)/fftools/resources/Makefile
+
 OBJS-ffmpeg +=                  \
     fftools/ffmpeg_dec.o        \
     fftools/ffmpeg_demux.o      \
@@ -19,8 +21,21 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_mux_init.o   \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
+    fftools/graph/graphprint.o        \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
+    fftools/textformat/avtextformat.o \
+    fftools/textformat/tf_compact.o   \
+    fftools/textformat/tf_default.o   \
+    fftools/textformat/tf_flat.o      \
+    fftools/textformat/tf_ini.o       \
+    fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
+    fftools/textformat/tf_xml.o       \
+    fftools/textformat/tw_avio.o      \
+    fftools/textformat/tw_buffer.o    \
+    fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffprobe +=                       \
     fftools/textformat/avtextformat.o \
@@ -29,10 +44,12 @@ OBJS-ffprobe +=                       \
     fftools/textformat/tf_flat.o      \
     fftools/textformat/tf_ini.o       \
     fftools/textformat/tf_json.o      \
+    fftools/textformat/tf_mermaid.o   \
     fftools/textformat/tf_xml.o       \
     fftools/textformat/tw_avio.o      \
     fftools/textformat/tw_buffer.o    \
     fftools/textformat/tw_stdout.o    \
+    $(OBJS-resman)                    \
 
 OBJS-ffplay += fftools/ffplay_renderer.o
 
@@ -42,7 +59,7 @@ ifdef HAVE_GNU_WINDRES
 OBJS-$(1) += fftools/fftoolsres.o
 endif
 $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
-$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
+$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources fftools/graph
 $$(OBJS-$(1)): CFLAGS  += $(CFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
 $(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -57,6 +74,7 @@ fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
 OUTDIRS += fftools
 OUTDIRS += fftools/textformat
 OUTDIRS += fftools/resources
+OUTDIRS += fftools/graph
 
 ifdef AVPROGS
 install: install-progs install-data
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index dc321fb4a2..6766ec209c 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -81,6 +81,7 @@
 #include "ffmpeg.h"
 #include "ffmpeg_sched.h"
 #include "ffmpeg_utils.h"
+#include "graph/graphprint.h"
 
 const char program_name[] = "ffmpeg";
 const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
+    if (print_graphs || print_graphs_file)
+        print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
+
     if (do_benchmark) {
         int64_t maxrss = getmaxrss() / 1024;
         av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 5869979214..7fbf0ad532 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -717,6 +717,9 @@ extern float max_error_rate;
 extern char *filter_nbthreads;
 extern int filter_complex_nbthreads;
 extern int vstats_version;
+extern int print_graphs;
+extern char *print_graphs_file;
+extern char *print_graphs_format;
 extern int auto_conversion_filters;
 
 extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index eab9487f97..b774606562 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -22,6 +22,7 @@
 
 #include "ffmpeg.h"
 #include "ffmpeg_filter.h"
+#include "graph/graphprint.h"
 
 #include "libavfilter/avfilter.h"
 #include "libavfilter/buffersink.h"
@@ -2983,6 +2984,10 @@ read_frames:
     }
 
 finish:
+
+    if (print_graphs || print_graphs_file)
+        print_filtergraph(fg, fgt.graph);
+
     // EOF is normal termination
     if (ret == AVERROR_EOF)
         ret = 0;
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 6ec325f51e..3d1efe32f9 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -47,6 +47,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/stereo3d.h"
+#include "graph/graphprint.h"
 
 HWDevice *filter_hw_device;
 
@@ -75,6 +76,9 @@ float max_error_rate  = 2.0/3;
 char *filter_nbthreads;
 int filter_complex_nbthreads = 0;
 int vstats_version = 2;
+int print_graphs = 0;
+char *print_graphs_file = NULL;
+char *print_graphs_format = NULL;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1735,6 +1739,15 @@ const OptionDef options[] = {
         { .func_arg = opt_filter_complex_script },
         "deprecated, use -/filter_complex instead", "filename" },
 #endif
+    { "print_graphs",   OPT_TYPE_BOOL, 0,
+        { &print_graphs },
+        "print execution graph data to stderr" },
+    { "print_graphs_file", OPT_TYPE_STRING, 0,
+        { &print_graphs_file },
+        "write execution graph data to the specified file", "filename" },
+    { "print_graphs_format", OPT_TYPE_STRING, 0,
+        { &print_graphs_format },
+      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
new file mode 100644
index 0000000000..05c06f80fb
--- /dev/null
+++ b/fftools/graph/graphprint.c
@@ -0,0 +1,1101 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * output writers for filtergraph details
+ */
+
+#include <string.h>
+#include <stdatomic.h>
+
+#include "graphprint.h"
+
+#include <libavformat/url.h>
+
+#include "fftools/ffmpeg_filter.h"
+#include "fftools/ffmpeg_mux.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/dict.h"
+#include "libavutil/common.h"
+#include "libavfilter/avfilter.h"
+#include "libavutil/buffer.h"
+#include "libavutil/hwcontext.h"
+#include "fftools/textformat/avtextformat.h"
+#include "fftools/textformat/tf_mermaid.h"
+#include "fftools/resources/resman.h"
+
+typedef enum {
+    SECTION_ID_ROOT,
+    SECTION_ID_FILTERGRAPHS,
+    SECTION_ID_FILTERGRAPH,
+    SECTION_ID_GRAPH_INPUTS,
+    SECTION_ID_GRAPH_INPUT,
+    SECTION_ID_GRAPH_OUTPUTS,
+    SECTION_ID_GRAPH_OUTPUT,
+    SECTION_ID_FILTERS,
+    SECTION_ID_FILTER,
+    SECTION_ID_FILTER_INPUTS,
+    SECTION_ID_FILTER_INPUT,
+    SECTION_ID_FILTER_OUTPUTS,
+    SECTION_ID_FILTER_OUTPUT,
+    SECTION_ID_HWFRAMESCONTEXT,
+    SECTION_ID_INPUTFILES,
+    SECTION_ID_INPUTFILE,
+    SECTION_ID_INPUTSTREAMS,
+    SECTION_ID_INPUTSTREAM,
+    SECTION_ID_OUTPUTFILES,
+    SECTION_ID_OUTPUTFILE,
+    SECTION_ID_OUTPUTSTREAMS,
+    SECTION_ID_OUTPUTSTREAM,
+    SECTION_ID_STREAMLINKS,
+    SECTION_ID_STREAMLINK,
+    SECTION_ID_DECODERS,
+    SECTION_ID_DECODER,
+    SECTION_ID_ENCODERS,
+    SECTION_ID_ENCODER,
+} SectionID;
+
+static struct AVTextFormatSection sections[] = {
+    [SECTION_ID_ROOT]            = { SECTION_ID_ROOT, "root", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER, { SECTION_ID_FILTERGRAPHS, SECTION_ID_INPUTFILES, SECTION_ID_OUTPUTFILES, SECTION_ID_DECODERS, SECTION_ID_ENCODERS, SECTION_ID_STREAMLINKS, -1 } },
+
+    [SECTION_ID_FILTERGRAPHS]    = { SECTION_ID_FILTERGRAPHS, "graphs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
+    [SECTION_ID_FILTERGRAPH]     = { SECTION_ID_FILTERGRAPH, "graph", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { SECTION_ID_GRAPH_INPUTS, SECTION_ID_GRAPH_OUTPUTS, SECTION_ID_FILTERS, -1 }, .element_name = "graph_info" },
+
+    [SECTION_ID_GRAPH_INPUTS]    = { SECTION_ID_GRAPH_INPUTS, "graph_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_INPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_INPUT]     = { SECTION_ID_GRAPH_INPUT, "graph_input", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_GRAPH_OUTPUTS]   = { SECTION_ID_GRAPH_OUTPUTS, "graph_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_OUTPUT, -1 }, .id_key = "id" },
+    [SECTION_ID_GRAPH_OUTPUT]    = { SECTION_ID_GRAPH_OUTPUT, "graph_output", 0, { -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTERS]         = { SECTION_ID_FILTERS, "filters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_FILTER, -1 }, .id_key = "graph_id" },
+    [SECTION_ID_FILTER]          = { SECTION_ID_FILTER, "filter", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { SECTION_ID_FILTER_INPUTS, SECTION_ID_FILTER_OUTPUTS, -1 }, .id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_INPUTS]   = { SECTION_ID_FILTER_INPUTS, "filter_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_INPUT, -1 } },
+    [SECTION_ID_FILTER_INPUT]    = { SECTION_ID_FILTER_INPUT, "filter_input", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "source_filter_id", .dest_id_key = "filter_id" },
+
+    [SECTION_ID_FILTER_OUTPUTS]  = { SECTION_ID_FILTER_OUTPUTS, "filter_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_OUTPUT, -1 } },
+    [SECTION_ID_FILTER_OUTPUT]   = { SECTION_ID_FILTER_OUTPUT, "filter_output", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "filter_id", .dest_id_key = "dest_filter_id" },
+
+    [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context",  0, { -1 }, },
+
+    [SECTION_ID_INPUTFILES]      = { SECTION_ID_INPUTFILES, "inputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTFILE]       = { SECTION_ID_INPUTFILE, "inputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_INPUTSTREAMS]    = { SECTION_ID_INPUTSTREAMS, "inputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_INPUTSTREAM]     = { SECTION_ID_INPUTSTREAM, "inputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTFILES]     = { SECTION_ID_OUTPUTFILES, "outputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTFILE, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTFILE]      = { SECTION_ID_OUTPUTFILE, "outputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAMS, -1 }, .id_key = "id" },
+
+    [SECTION_ID_OUTPUTSTREAMS]   = { SECTION_ID_OUTPUTSTREAMS, "outputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAM, -1 }, .id_key = "id" },
+    [SECTION_ID_OUTPUTSTREAM]    = { SECTION_ID_OUTPUTSTREAM, "outputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id", },
+
+    [SECTION_ID_STREAMLINKS]     = { SECTION_ID_STREAMLINKS, "streamlinks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAMLINK, -1 } },
+    [SECTION_ID_STREAMLINK]      = { SECTION_ID_STREAMLINK, "streamlink", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .src_id_key = "source_stream_id", .dest_id_key = "dest_stream_id" },
+
+    [SECTION_ID_DECODERS]        = { SECTION_ID_DECODERS, "decoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_DECODER, -1 } },
+    [SECTION_ID_DECODER]         = { SECTION_ID_DECODER, "decoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "source_id", .dest_id_key = "id" },
+
+    [SECTION_ID_ENCODERS]        = { SECTION_ID_ENCODERS, "encoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_ENCODER, -1 } },
+    [SECTION_ID_ENCODER]         = { SECTION_ID_ENCODER, "encoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "id", .dest_id_key = "dest_id" },
+};
+
+typedef struct GraphPrintContext {
+    AVTextFormatContext *tfc;
+    AVTextWriterContext *wctx;
+    AVDiagramConfig diagram_config;
+
+    int id_prefix_num;
+    int is_diagram;
+    int opt_flags;
+    int skip_buffer_filters;
+    AVBPrint pbuf;
+
+} GraphPrintContext;
+
+/* Text Format API Shortcuts */
+#define print_id(k, v)          print_sanizied_id(gpc, k, v, 0)
+#define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
+#define print_int(k, v)         avtext_print_integer(tfc, k, v)
+#define print_int_opt(k, v)     avtext_print_integer_flags(tfc, k, v, gpc->opt_flags)
+#define print_q(k, v, s)        avtext_print_rational(tfc, k, v, s)
+#define print_str(k, v)         avtext_print_string(tfc, k, v, 0)
+#define print_str_opt(k, v)     avtext_print_string(tfc, k, v, gpc->opt_flags)
+#define print_val(k, v, u)      avtext_print_unit_int(tfc, k, v, u)
+
+#define print_fmt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, 0);    \
+} while (0)
+
+#define print_fmt_opt(k, f, ...) do {              \
+    av_bprint_clear(&gpc->pbuf);                    \
+    av_bprintf(&gpc->pbuf, f, __VA_ARGS__);         \
+    avtext_print_string(tfc, k, gpc->pbuf.str, gpc->opt_flags);    \
+} while (0)
+
+
+static atomic_int prefix_num = 0;
+
+static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
+{
+    unsigned i;
+    for (i = 0; src[i] && i < dst_size - 1; i++)
+        dst[i]      = (char)av_toupper(src[i]);
+    dst[i] = 0;
+    return dst;
+}
+
+static char *get_extension(const char *url)
+{
+    const char *ext;
+    URLComponents uc;
+    int ret;
+    char scratchpad[128];
+
+    if (!url)
+        return 0;
+
+    ret = ff_url_decompose(&uc, url, NULL);
+    if (ret < 0)
+        return NULL;
+    for (ext = uc.query; *ext != '.' && ext > uc.path; ext--) {
+    }
+
+    if (*ext != '.')
+        return 0;
+    if (uc.query - ext > sizeof(scratchpad))
+        return NULL; //not enough memory in our scratchpad
+    av_strlcpy(scratchpad, ext + 1, uc.query - ext);
+
+    return av_strdup(scratchpad);
+}
+
+static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+
+    if (!hw_device_context)
+        return;
+
+    print_int_opt("has_hw_device_context", 1);
+    print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
+}
+
+static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    const AVPixFmtDescriptor *pix_desc_hw;
+    const AVPixFmtDescriptor *pix_desc_sw;
+
+    if (!hw_frames_context || !hw_frames_context->device_ctx)
+        return;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT);
+
+    print_int_opt("has_hw_frames_context", 1);
+    print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
+
+    pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
+    if (pix_desc_hw) {
+        print_str("hw_pixel_format", pix_desc_hw->name);
+        if (pix_desc_hw->alias)
+            print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
+    }
+
+    pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
+    if (pix_desc_sw) {
+        print_str("sw_pixel_format", pix_desc_sw->name);
+        if (pix_desc_sw->alias)
+            print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
+    }
+
+    print_int_opt("width", hw_frames_context->width);
+    print_int_opt("height", hw_frames_context->height);
+    print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT
+}
+
+static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBufferRef *hw_frames_ctx;
+    char layout_string[64];
+
+    if (!link)
+        return;
+
+    hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
+
+    print_str_opt("media_type", av_get_media_type_string(link->type));
+
+    switch (link->type) {
+    case AVMEDIA_TYPE_VIDEO:
+
+        if (hw_frames_ctx && hw_frames_ctx->data) {
+            AVHWFramesContext *      hwfctx      = (AVHWFramesContext *)hw_frames_ctx->data;
+            const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
+            const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
+            if (pix_desc_hw && pix_desc_sw)
+                print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
+        } else {
+            print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
+        }
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        print_q("sar", link->sample_aspect_ratio, ':');
+
+        if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
+            print_str_opt("color_range", av_color_range_name(link->color_range));
+
+        if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
+            print_str("color_space", av_color_space_name(link->colorspace));
+        break;
+
+    case AVMEDIA_TYPE_SUBTITLE:
+        ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
+
+        if (link->w && link->h) {
+            if (tfc->show_value_unit) {
+                print_fmt("size", "%dx%d", link->w, link->h);
+            } else {
+                print_int("width", link->w);
+                print_int("height", link->h);
+            }
+        }
+
+        break;
+
+    case AVMEDIA_TYPE_AUDIO:
+        av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
+        print_str("channel_layout", layout_string);
+        print_val("channels", link->ch_layout.nb_channels, "ch");
+        if (tfc->show_value_unit)
+            print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
+        else
+            print_val("sample_rate", link->sample_rate, "Hz");
+
+        break;
+    }
+
+    print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
+
+    if (hw_frames_ctx && hw_frames_ctx->data)
+        print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
+}
+
+static char sanitize_char(const char c)
+{
+    if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+        return c;
+    return '_';
+}
+
+static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVBPrint buf;
+
+    if (!key || !id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    print_str(key, buf.str);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+    AVBPrint buf;
+
+    if (!id_str)
+        return;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!skip_prefix)
+        av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
+
+    // sanizize section id
+    for (const char *p = id_str; *p; p++)
+        av_bprint_chars(&buf, sanitize_char(*p), 1);
+
+    sec_ctx.context_id = buf.str;
+
+    avtext_print_section_header(tfc, &sec_ctx, section_id);
+
+    av_bprint_finalize(&buf, NULL);
+}
+
+static const char *get_filterpad_name(const AVFilterPad *pad)
+{
+    return pad ? avfilter_pad_get_name(pad, 0) : "pad";
+}
+
+static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
+
+    ////print_id("filter_id", filter->name);
+
+    if (filter->filter) {
+        print_str("filter_name", filter->filter->name);
+        print_str_opt("description", filter->filter->description);
+        print_int_opt("nb_inputs", filter->nb_inputs);
+        print_int_opt("nb_outputs", filter->nb_outputs);
+    }
+
+    if (filter->hw_device_ctx) {
+        AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
+        print_hwdevicecontext(gpc, device_context);
+        if (filter->extra_hw_frames > 0)
+            print_int("extra_hw_frames", filter->extra_hw_frames);
+    }
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
+
+    for (unsigned i = 0; i < filter->nb_inputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->inputs[i];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
+        sec_ctx.context_type = NULL;
+
+        print_int_opt("input_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
+
+        dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
+        if (dic_entry) {
+            char buf[256];
+            (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
+            print_id_noprefix("source_filter_id", buf);
+        } else {
+            print_id("source_filter_id", link->src->name);
+        }
+
+        print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
+
+    for (unsigned i = 0; i < filter->nb_outputs; i++) {
+        AVDictionaryEntry *dic_entry;
+        AVFilterLink *link = filter->outputs[i];
+        char buf[256];
+
+        sec_ctx.context_type = av_get_media_type_string(link->type);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
+        sec_ctx.context_type = NULL;
+
+        dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
+        if (dic_entry) {
+            (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
+            print_id_noprefix("dest_filter_id", buf);
+        } else {
+            print_id("dest_filter_id", link->dst->name);
+        }
+
+        print_int_opt("output_index", i);
+        print_str_opt("pad_name", get_filterpad_name(link->srcpad));
+        ////print_id("dest_filter_id", link->dst->name);
+        print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
+        print_id("filter_id", filter->name);
+
+        print_link(gpc, link);
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTER
+}
+
+static void init_sections(void)
+{
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
+        sections[i].show_all_entries = 1;
+}
+
+static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
+{
+    AVTextFormatContext *tfc = gpc->tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVDictionary *input_map = NULL;
+    AVDictionary *output_map = NULL;
+
+    print_int("graph_index", fg->index);
+    print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+    print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
+    print_str("description", fgp->graph_desc);
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
+
+    for (int i = 0; i < fg->nb_inputs; i++) {
+        InputFilterPriv *ifilter = ifp_from_ifilter(fg->inputs[i]);
+        enum AVMediaType media_type = ifilter->type;
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
+
+        print_int("input_index", ifilter->index);
+
+        if (ifilter->linklabel)
+            print_str("link_label", (const char*)ifilter->linklabel);
+
+        if (ifilter->filter) {
+            print_id("filter_id", ifilter->filter->name);
+            print_str("filter_name", ifilter->filter->filter->name);
+        }
+
+        if (ifilter->linklabel && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
+        else if (ifilter->opts.name && ifilter->filter)
+            av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->opts.name, 0);
+
+        print_str("media_type", av_get_media_type_string(media_type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
+
+    print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
+
+    for (int i = 0; i < fg->nb_outputs; i++) {
+        OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
+
+        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
+
+        print_int("output_index", ofilter->index);
+
+        print_str("name", ofilter->name);
+
+        if (fg->outputs[i]->linklabel)
+            print_str("link_label", (const char*)fg->outputs[i]->linklabel);
+
+        if (ofilter->filter) {
+            print_id("filter_id", ofilter->filter->name);
+            print_str("filter_name", ofilter->filter->filter->name);
+        }
+
+        if (ofilter->name && ofilter->filter)
+            av_dict_set(&output_map, ofilter->filter->name, ofilter->name, 0);
+
+
+        print_str("media_type", av_get_media_type_string(fg->outputs[i]->type));
+
+        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
+
+    if (graph) {
+        AVTextFormatSectionContext sec_ctx = { 0 };
+
+        sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTERS);
+
+        if (gpc->is_diagram) {
+            print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
+            print_str("description", fgp->graph_desc);
+            print_str("id", sec_ctx.context_id);
+        }
+
+        av_freep(&sec_ctx.context_id);
+
+        for (unsigned i = 0; i < graph->nb_filters; i++) {
+            AVFilterContext *filter = graph->filters[i];
+
+            if (gpc->skip_buffer_filters) {
+                if (av_dict_get(input_map, filter->name, NULL, 0))
+                    continue;
+                if (av_dict_get(output_map, filter->name, NULL, 0))
+                    continue;
+            }
+
+            sec_ctx.context_id = filter->name;
+
+            print_filter(gpc, filter, input_map, output_map);
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
+    }
+
+    // Clean up dictionaries
+    av_dict_free(&input_map);
+    av_dict_free(&output_map);
+}
+
+static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    AVTextFormatContext       *tfc = gpc->tfc;
+    AVBPrint                   buf;
+    AVTextFormatSectionContext sec_ctx = { 0 };
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+    print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
+
+    for (int n = nb_ifiles - 1; n >= 0; n--) {
+        InputFile *ifi = ifiles[n];
+        AVFormatContext *fc = ifi->ctx;
+
+        sec_ctx.context_id = av_asprintf("Input_%d", n);
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
+        av_freep(&sec_ctx.context_id);
+
+        print_fmt("index", "%d", ifi->index);
+
+        if (fc) {
+            print_str("demuxer_name", fc->iformat->name);
+            if (fc->url) {
+                char *extension = get_extension(fc->url);
+                if (extension) {
+                    print_str("file_extension", extension);
+                    av_freep(&extension);
+                }
+                print_str("url", fc->url);
+            }
+        }
+
+        sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
+
+        av_freep(&sec_ctx.context_id);
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+            const AVCodecDescriptor *codec_desc;
+
+            if (!ist || !ist->par)
+                continue;
+
+            codec_desc = avcodec_descriptor_get(ist->par->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
+
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_in_%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else if (ist->dec) {
+                char char_buf[256];
+                av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
+                av_bprintf(&buf, "%s", "Attachment");
+            } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
+                av_bprintf(&buf, "%s", "Data");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ist->index);
+
+            if (ist->dec)
+                print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
+
+
+    print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
+
+    for (int n = 0; n < nb_ifiles; n++) {
+        InputFile *ifi = ifiles[n];
+
+        for (int i = 0; i < ifi->nb_streams; i++) {
+            InputStream *ist = ifi->streams[i];
+
+            if (!ist->decoder)
+                continue;
+
+            sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_DECODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("source_id", "r_in_%d_%d", n, i);
+            print_fmt("id", "in_%d_%d", n, i);
+
+            ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
+            print_fmt("name", "%s", ist->dec->name);
+
+            print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_DECODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            ////const AVCodecDescriptor *codec_desc;
+
+            if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
+                continue;
+
+            ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            sec_ctx.context_flags = 2;
+
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_ENCODER);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+            sec_ctx.context_flags = 0;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "out__%d_%d", n, i);
+            print_fmt("dest_id", "r_out__%d_%d", n, i);
+
+            print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
+
+
+    print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
+
+    for (int n = nb_ofiles - 1; n >= 0; n--) {
+        OutputFile *of = ofiles[n];
+        Muxer *muxer = (Muxer *)of;
+
+        if (!muxer->fc)
+            continue;
+
+        sec_ctx.context_id = av_asprintf("Output_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
+
+        av_freep(&sec_ctx.context_id);
+
+        ////print_str_opt("index", av_get_media_type_string(of->index));
+        print_fmt("index", "%d", of->index);
+        ////print_str("url", of->url);
+        print_str("muxer_name", muxer->fc->oformat->name);
+        if (of->url) {
+            char *extension = get_extension(of->url);
+            if (extension) {
+                print_str("file_extension", extension);
+                av_freep(&extension);
+            }
+            print_str("url", of->url);
+        }
+
+        sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
+
+        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+            const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
+
+            sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
+            sec_ctx.context_type = av_get_media_type_string(ost->type);
+            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
+            av_freep(&sec_ctx.context_id);
+            sec_ctx.context_type = NULL;
+
+            av_bprint_clear(&buf);
+
+            print_fmt("id", "r_out__%d_%d", n, i);
+
+            if (codec_desc && codec_desc->name) {
+                av_bprintf(&buf, "%s", codec_desc->long_name);
+            } else {
+                av_bprintf(&buf, "%s", "unknown");
+            }
+
+            print_fmt("name", "%s", buf.str);
+            print_fmt("index", "%d", ost->index);
+
+            print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+            avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
+        }
+
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
+        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
+
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->ist && !ost->filter) {
+                sec_ctx.context_type = av_get_media_type_string(ost->type);
+                avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_STREAMLINK);
+                sec_ctx.context_type = NULL;
+
+                if (ost->enc) {
+                    print_fmt("dest_stream_id", "out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Transcode");
+                } else {
+                    print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
+                    print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
+                    print_str("operation", "Stream Copy");
+                }
+
+                print_str_opt("media_type", av_get_media_type_string(ost->type));
+
+                avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
+
+    return 0;
+}
+
+
+static void uninit_graphprint(GraphPrintContext *gpc)
+{
+    if (gpc->tfc)
+        avtext_context_close(&gpc->tfc);
+
+    if (gpc->wctx)
+        avtextwriter_context_close(&gpc->wctx);
+
+    // Finalize the print buffer if it was initialized
+    av_bprint_finalize(&gpc->pbuf, NULL);
+}
+
+static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
+{
+    const AVTextFormatter *text_formatter;
+    AVTextFormatContext *tfc = NULL;
+    AVTextWriterContext *wctx = NULL;
+    GraphPrintContext *gpc = NULL;
+    char *w_args = NULL;
+    char *w_name;
+    int ret;
+
+    init_sections();
+    *pgpc = NULL;
+
+    av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    if (!print_graphs_format)
+        print_graphs_format = av_strdup("json");
+    if (!print_graphs_format) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    w_name = av_strtok(print_graphs_format, "=", &w_args);
+    if (!w_name) {
+        av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    text_formatter = avtext_get_formatter_by_name(w_name);
+    if (!text_formatter) {
+        av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    ret = avtextwriter_create_buffer(&wctx, target_buf);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    AVTextFormatOptions tf_options = { .show_optional_fields = -1 };
+    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), tf_options, NULL);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    gpc = av_mallocz(sizeof(GraphPrintContext));
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    gpc->wctx = wctx;
+    gpc->tfc = tfc;
+    av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
+    gpc->is_diagram = !!(tfc->formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
+    if (gpc->is_diagram) {
+        tfc->show_value_unit = 1;
+        tfc->show_optional_fields = -1;
+        gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+        gpc->skip_buffer_filters = 1;
+        ////} else {
+        ////    gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
+    }
+
+    if (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml")) {
+        gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
+
+        if (!strcmp(text_formatter->name, "mermaidhtml"))
+            gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
+
+        av_diagram_init(tfc, &gpc->diagram_config);
+    }
+
+    *pgpc = gpc;
+
+    return 0;
+
+fail:
+    if (tfc)
+        avtext_context_close(&tfc);
+    if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
+        avtextwriter_context_close(&wctx);
+    av_freep(&gpc);
+
+    return ret;
+}
+
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    FilterGraphPriv *fgp = fgp_from_fg(fg);
+    AVBPrint *target_buf = &fgp->graph_print_buf;
+    int ret;
+
+    if (!fg || !fgp) {
+        av_log(NULL, AV_LOG_ERROR, "Invalid filter graph provided\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (target_buf->len)
+        av_bprint_finalize(target_buf, NULL);
+
+    ret = init_graphprint(&gpc, target_buf);
+    if (ret)
+        return ret;
+
+    if (!gpc) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to initialize graph print context\n");
+        return AVERROR(ENOMEM);
+    }
+
+    tfc = gpc->tfc;
+
+    // Due to the threading model each graph needs to print itself into a buffer
+    // from its own thread. The actual printing happens short before cleanup in ffmpeg.c
+    // where all graphs are assembled together. To make this work, we need to put the
+    // formatting context into the same state like it would be when printing all at once,
+    // so here we print the section headers and clear the buffer to get into the right state.
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+
+    av_bprint_clear(target_buf);
+
+    print_filtergraph_single(gpc, fg, graph);
+
+    if (gpc->is_diagram) {
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+    }
+
+    uninit_graphprint(gpc);
+
+    return 0;
+}
+
+static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    GraphPrintContext *gpc = NULL;
+    AVTextFormatContext *tfc;
+    AVBPrint target_buf;
+    int ret;
+
+    ret = init_graphprint(&gpc, &target_buf);
+    if (ret)
+        goto cleanup;
+
+    if (!gpc) {
+        ret = AVERROR(ENOMEM);
+        goto cleanup;
+    }
+
+    tfc = gpc->tfc;
+
+    avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
+    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
+
+    for (int i = 0; i < nb_graphs; i++) {
+        FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
+        AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+        if (graph_buf->len > 0) {
+            avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+            av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+            av_bprint_finalize(graph_buf, NULL);
+            avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+        }
+    }
+
+    for (int n = 0; n < nb_ofiles; n++) {
+        OutputFile *of = ofiles[n];
+
+        for (int i = 0; i < of->nb_streams; i++) {
+            OutputStream *ost = of->streams[i];
+
+            if (ost->fg_simple) {
+                FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
+                AVBPrint *graph_buf = &fgp->graph_print_buf;
+
+                if (graph_buf->len > 0) {
+                    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
+                    av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
+                    av_bprint_finalize(graph_buf, NULL);
+                    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
+                }
+            }
+        }
+    }
+
+    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+
+    print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    avtext_print_section_footer(tfc); // SECTION_ID_ROOT
+
+    if (print_graphs_file) {
+        AVIOContext *avio = NULL;
+
+        if (!strcmp(print_graphs_file, "-")) {
+            printf("%s", target_buf.str);
+        } else {
+            ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
+            if (ret < 0) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
+                goto cleanup;
+            }
+
+            avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
+            avio_flush(avio);
+
+            if ((ret = avio_closep(&avio)) < 0)
+                av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
+        }
+    }
+
+    if (print_graphs)
+        av_log(NULL, AV_LOG_INFO, "%s    %c", target_buf.str, '\n');
+
+cleanup:
+    // Properly clean up resources
+    if (gpc)
+        uninit_graphprint(gpc);
+
+    // Ensure the target buffer is properly finalized
+    av_bprint_finalize(&target_buf, NULL);
+
+    return ret;
+}
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
+{
+    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+}
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
new file mode 100644
index 0000000000..9f043cc273
--- /dev/null
+++ b/fftools/graph/graphprint.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2025 - softworkz
+ *
+ * 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_GRAPH_GRAPHPRINT_H
+#define FFTOOLS_GRAPH_GRAPHPRINT_H
+
+#include "fftools/ffmpeg.h"
+
+int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles);
+
+int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+
+#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c
index 86220dc676..f41c395f07 100644
--- a/fftools/textformat/avtextformat.c
+++ b/fftools/textformat/avtextformat.c
@@ -697,6 +697,8 @@ static void formatters_register_all(void)
     registered_formatters[4] = &avtextformatter_ini;
     registered_formatters[5] = &avtextformatter_json;
     registered_formatters[6] = &avtextformatter_xml;
+    registered_formatters[7] = &avtextformatter_mermaid;
+    registered_formatters[8] = &avtextformatter_mermaidhtml;
 }
 
 const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h
index f4175c6f48..43450148fe 100644
--- a/fftools/textformat/avtextformat.h
+++ b/fftools/textformat/avtextformat.h
@@ -31,6 +31,12 @@
 
 #define SECTION_MAX_NB_CHILDREN 11
 
+typedef struct AVTextFormatSectionContext {
+    char *context_id;
+    const char *context_type;
+    int context_flags;
+} AVTextFormatSectionContext;
+
 
 typedef struct AVTextFormatSection {
     int id;             ///< unique id identifying a section
@@ -42,6 +48,10 @@ typedef struct AVTextFormatSection {
                                            ///  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
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE       32 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS      64 ///< ...
+#define AV_TEXTFORMAT_SECTION_PRINT_TAGS         128 ///< ...
+#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH   256 ///< ...
 
     int flags;
     const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
@@ -50,12 +60,17 @@ typedef struct AVTextFormatSection {
     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;
+    const char *id_key;          ///< name of the key to be used as the id 
+    const char *src_id_key;     ///< name of the key to be used as the source id for diagram connections
+    const char *dest_id_key;   ///< name of the key to be used as the target id for diagram connections
+    const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
 } AVTextFormatSection;
 
 typedef struct AVTextFormatContext AVTextFormatContext;
 
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
 #define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
+#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER         4
 
 typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
@@ -64,6 +79,18 @@ typedef enum {
     AV_TEXTFORMAT_STRING_VALIDATION_NB
 } StringValidation;
 
+typedef enum {
+    AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_NONDIR,
+    AV_TEXTFORMAT_LINKTYPE_HIDDEN,
+    AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
+    AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
+    AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
+} AVTextFormatLinkType;
+
 typedef struct AVTextFormatter {
     const AVClass *priv_class;      ///< private class of the formatter, if any
     int priv_size;                  ///< private size for the formatter context
@@ -169,5 +196,7 @@ extern const AVTextFormatter avtextformatter_flat;
 extern const AVTextFormatter avtextformatter_ini;
 extern const AVTextFormatter avtextformatter_json;
 extern const AVTextFormatter avtextformatter_xml;
+extern const AVTextFormatter avtextformatter_mermaid;
+extern const AVTextFormatter avtextformatter_mermaidhtml;
 
 #endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
diff --git a/fftools/textformat/tf_mermaid.c b/fftools/textformat/tf_mermaid.c
new file mode 100644
index 0000000000..6147cf6eea
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.c
@@ -0,0 +1,658 @@
+/*
+ * 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 "tf_internal.h"
+#include "tf_mermaid.h"
+#include <libavutil/mem.h>
+#include <libavutil/avassert.h>
+#include <libavutil/bprint.h>
+#include <libavutil/opt.h>
+
+
+static const char *init_directive = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 10,"
+        "\"nodeSpacing\": 10,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"flowchart\": { "
+            "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
+            "\"diagramPadding\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char* init_directive_er = ""
+    "%%{init: {"
+        "\"theme\": \"base\","
+        "\"layout\": \"elk\","
+        "\"curve\": \"monotoneX\","
+        "\"rankSpacing\": 65,"
+        "\"nodeSpacing\": 60,"
+        "\"themeCSS\": \"__###__\","
+        "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
+        "\"themeVariables\": { "
+            "\"clusterBkg\": \"white\", "
+            "\"primaryBorderColor\": \"gray\", "
+            "\"lineColor\": \"gray\", "
+            "\"secondaryTextColor\": \"gray\", "
+            "\"tertiaryBorderColor\": \"gray\", "
+            "\"primaryTextColor\": \"#666\", "
+            "\"secondaryTextColor\": \"red\" "
+        "},"
+        "\"er\": { "
+            "\"diagramPadding\": 12, "
+            "\"entityPadding\": 4, "
+            "\"minEntityWidth\": 150, "
+            "\"minEntityHeight\": 20, "
+            "\"curve\": \"monotoneX\" "
+        "}"
+    " }}%%\n\n";
+
+static const char *theme_css_er = ""
+
+    // Variables
+            ".root { "
+                "--ff-colvideo: #6eaa7b; "
+                "--ff-colaudio: #477fb3; "
+                "--ff-colsubtitle: #ad76ab; "
+                "--ff-coltext: #666; "
+            "} "
+            " g.nodes g.node.default rect.basic.label-container, "
+            " g.nodes g.node.default path { "
+            "     rx: 1; "
+            "     ry: 1; "
+            "     stroke-width: 1px !important; "
+            "     stroke: #e9e9e9 !important; "
+            "     fill: url(#ff-filtergradient) !important; "
+            "     filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
+            "     fill: white !important; "
+            " } "
+            "  "
+            " .relationshipLine { "
+            "     stroke: gray; "
+            "     stroke-width: 1; "
+            "     fill: none; "
+            "     filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
+            " } "
+            "  "
+            " g.node.default g.label.name  foreignObject > div > span > p, "
+            " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
+            "     font-size: 0.95rem; "
+            "     font-weight: 500; "
+            "     text-transform: uppercase; "
+            "     min-width: 5.5rem; "
+            "     margin-bottom: 0.5rem; "
+            "      "
+            " } "
+            "  "
+            " .edgePaths path { "
+            "     marker-end: none; "
+            "     marker-start: none; "
+            "  "
+            "} ";
+
+
+/* Mermaid Graph output */
+
+typedef struct MermaidContext {
+    const AVClass *class;
+    AVDiagramConfig *diagram_config;
+    int subgraph_count;
+    int within_tag;
+    int indent_level;
+    int create_html;
+
+    // Options
+    int enable_link_colors; // Requires Mermaid 11.5
+
+    struct section_data {
+        const char *section_id;
+        const char *section_type;
+        const char *src_id;
+        const char *dest_id;
+        AVTextFormatLinkType link_type;
+        int current_is_textblock;
+        int current_is_stadium;
+        int subgraph_start_incomplete;
+    }  section_data[SECTION_MAX_NB_LEVELS];
+
+    unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
+    AVBPrint link_buf; ///< print buffer for writing diagram links
+    AVDictionary *link_dict;
+} MermaidContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(MermaidContext, x)
+
+static const AVOption mermaid_options[] = {
+    { "link_coloring",    "enable colored links (requires Mermaid >= 11.5)",  OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL,   { .i64 = 1 },  0, 1 },
+    ////{"diagram_css",      "CSS for the diagram",                              OFFSET(diagram_css),        AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    ////{"html_template",    "Template HTML",                                    OFFSET(html_template),      AV_OPT_TYPE_STRING, {.i64=0},  0, 1 },
+    { NULL },
+};
+
+DEFINE_FORMATTER_CLASS(mermaid);
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
+{
+    MermaidContext *mmc = tfc->priv;
+    mmc->diagram_config = diagram_config;
+}
+
+static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
+{
+    MermaidContext *mmc = tfc->priv;
+    AVBPrint buf;
+
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    av_bprintf(&buf, "%s--%s", src, dest);
+
+    if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
+        return 1;
+
+    av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
+
+    return 0;
+}
+
+static av_cold int mermaid_init(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
+    return 0;
+}
+
+static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+
+    int ret = mermaid_init(tfc);
+
+    if (ret < 0)
+        return ret;
+
+    mmc->create_html = 1;
+
+    return 0;
+}
+
+#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
+
+static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
+{
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+    const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSectionContext *sec_ctx = data;
+
+    if (tfc->level == 0) {
+        char *directive;
+        AVBPrint css_buf;
+        const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
+        char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
+        (void)theme_css_er;
+        ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
+        av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+        av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
+        av_freep(&single_line_css);
+
+        directive = av_strireplace(diag_directive, "__###__", css_buf.str);
+        if (mmc->create_html) {
+            uint64_t length;
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+
+            length = token_pos - mmc->diagram_config->html_template;
+            for (uint64_t i = 0; i < length; i++)
+                writer_w8(tfc, mmc->diagram_config->html_template[i]);
+        }
+
+        writer_put_str(tfc, directive);
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+            writer_put_str(tfc, "flowchart LR\n");
+        ////writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
+            writer_put_str(tfc, "  gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%);     \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%);   \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%);     \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            writer_put_str(tfc, "erDiagram\n");
+            break;
+        }
+
+        return;
+    }
+
+    if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
+        AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
+
+        if (parent_sec_data.subgraph_start_incomplete) {
+
+            if (parent_buf->len > 0)
+                writer_printf(tfc, "%s", parent_buf->str);
+
+            writer_put_str(tfc, "</div>\"]\n");
+
+            mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
+        }
+    }
+
+    av_freep(&mmc->section_data[tfc->level].section_id);
+    av_freep(&mmc->section_data[tfc->level].section_type);
+    av_freep(&mmc->section_data[tfc->level].src_id);
+    av_freep(&mmc->section_data[tfc->level].dest_id);
+    mmc->section_data[tfc->level].current_is_textblock = 0;
+    mmc->section_data[tfc->level].current_is_stadium = 0;
+    mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
+    mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
+
+    // NOTE: av_strdup() allocations aren't checked
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+            MM_INDENT();
+            writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        av_bprint_clear(buf);
+        writer_put_str(tfc, "\n");
+
+        mmc->indent_level++;
+
+        if (sec_ctx->context_id) {
+
+            mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+                if (sec_ctx->context_flags & 1) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_textblock = 1;
+                } else if (sec_ctx->context_flags & 2) {
+
+                    MM_INDENT();
+                    writer_printf(tfc, "%s([\"", sec_ctx->context_id);
+                    mmc->section_data[tfc->level].current_is_stadium = 1;
+                } else {
+                    MM_INDENT();
+                    writer_printf(tfc, "%s(\"", sec_ctx->context_id);
+                }
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+                MM_INDENT();
+                writer_printf(tfc, "%s {\n", sec_ctx->context_id);
+                break;
+            }
+
+        } else {
+            av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
+        }
+
+        mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
+
+        if (sec_ctx && sec_ctx->context_type)
+            writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
+        else
+            writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
+    }
+
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        av_bprint_clear(buf);
+        mmc->nb_link_captions[tfc->level] = 0;
+
+        if (sec_ctx && sec_ctx->context_type)
+            mmc->section_data[tfc->level].section_type = av_strdup(sec_ctx->context_type);
+
+        ////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(tfc, " type=\"%s\"", buf.str);
+    }
+}
+
+static void mermaid_print_section_footer(AVTextFormatContext *tfc)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
+        writer_put_str(tfc, "</div>");
+
+    if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (sec_data.current_is_textblock) {
+                writer_printf(tfc, "\"}\n", section->name);
+
+                if (sec_data.section_id) {
+                    MM_INDENT();
+                    writer_put_str(tfc, "class ");
+                    writer_put_str(tfc, sec_data.section_id);
+                    writer_put_str(tfc, " ff-");
+                    writer_put_str(tfc, section->name);
+                    writer_put_str(tfc, "\n");
+                }
+            } else if (sec_data.current_is_stadium) {
+                writer_printf(tfc, "\"]):::ff-%s\n", section->name);
+            } else {
+                writer_printf(tfc, "\"):::ff-%s\n", section->name);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+            MM_INDENT();
+            writer_put_str(tfc, "}\n\n");
+            break;
+        }
+
+        mmc->indent_level--;
+
+    } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
+
+        MM_INDENT();
+        writer_put_str(tfc, "end\n");
+
+        if (sec_data.section_id) {
+            MM_INDENT();
+            writer_put_str(tfc, "class ");
+            writer_put_str(tfc, sec_data.section_id);
+            writer_put_str(tfc, " ff-");
+            writer_put_str(tfc, section->name);
+            writer_put_str(tfc, "\n");
+        }
+
+        mmc->indent_level--;
+    }
+
+    if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
+        if (sec_data.src_id && sec_data.dest_id
+            && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
+            switch (mmc->diagram_config->diagram_type) {
+            case AV_DIAGRAMTYPE_GRAPH:
+
+                if (sec_data.section_type && mmc->enable_link_colors)
+                    av_bprintf(&mmc->link_buf, "\n  %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
+                else
+                    av_bprintf(&mmc->link_buf, "\n  %s ==", sec_data.src_id);
+
+                if (buf->len > 0) {
+                    av_bprintf(&mmc->link_buf, " \"%s", buf->str);
+
+                    for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
+                        av_bprintf(&mmc->link_buf, "<br>&nbsp;");
+
+                    av_bprintf(&mmc->link_buf, "\" ==");
+                }
+
+                av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
+
+                break;
+            case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+
+                av_bprintf(&mmc->link_buf, "\n  %s", sec_data.src_id);
+
+                switch (sec_data.link_type) {
+                case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
+                    av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
+                    break;
+                default:
+                    av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
+                    break;
+                }
+
+                av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
+
+                break;
+            }
+
+    if (tfc->level == 0) {
+
+        writer_put_str(tfc, "\n");
+        if (mmc->create_html) {
+            char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
+            if (!token_pos) {
+                av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
+                return;
+            }
+            token_pos += strlen("__###__");
+            writer_put_str(tfc, token_pos);
+        }
+    }
+
+    if (tfc->level == 1) {
+
+        if (mmc->link_buf.len > 0) {
+            writer_put_str(tfc, mmc->link_buf.str);
+            av_bprint_clear(&mmc->link_buf);
+        }
+
+        writer_put_str(tfc, "\n");
+    }
+}
+
+static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
+                                const char *str, int64_t num, const int is_int)
+{
+    MermaidContext *mmc = tfc->priv;
+    const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
+
+    if (!section)
+        return;
+
+    AVBPrint *buf = &tfc->section_pbuf[tfc->level];
+    struct section_data sec_data = mmc->section_data[tfc->level];
+    int exit = 0;
+
+    if (section->id_key && !strcmp(section->id_key, key)) {
+        mmc->section_data[tfc->level].section_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
+        mmc->section_data[tfc->level].dest_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->src_id_key && !strcmp(section->src_id_key, key)) {
+        mmc->section_data[tfc->level].src_id = av_strdup(str);
+        exit = 1;
+    }
+
+    if (section->linktype_key && !strcmp(section->linktype_key, key)) {
+        mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
+        exit = 1;
+    }
+
+    //if (exit)
+    //    return;
+
+    if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
+        || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
+
+        if (exit)
+            return;
+
+        switch (mmc->diagram_config->diagram_type) {
+        case AV_DIAGRAMTYPE_GRAPH:
+
+            if (is_int) {
+                writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
+            } else {
+                ////AVBPrint b;
+                ////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
+                const char *tmp = av_strireplace(str, "\"", "'");
+                ////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
+                writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
+                av_freep(&tmp);
+            }
+
+            break;
+        case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
+
+            if (!is_int && str)
+            {
+                const char *col_type;
+
+                if (key[0] == '_')
+                    return;
+
+                if (sec_data.section_id && !strcmp(str, sec_data.section_id))
+                    col_type = "PK";
+                else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
+                    col_type = "FK";
+                else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
+                    col_type = "FK";
+                else
+                    col_type = "";
+
+                MM_INDENT();
+
+                if (is_int)
+                    writer_printf(tfc, "    %s %"PRId64" %s\n", key, num, col_type);
+                else
+                    writer_printf(tfc, "    %s %s %s\n", key, str, col_type);
+            }
+            break;
+        }
+
+    } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
+
+        if (exit)
+            return;
+
+        if (buf->len > 0)
+            av_bprintf(buf, "%s", "<br>");
+
+        av_bprintf(buf, "");
+        if (is_int)
+            av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
+        else
+            av_bprintf(buf, "<span>%s</span>", str);
+
+        mmc->nb_link_captions[tfc->level]++;
+    }
+}
+
+static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
+{
+    mermaid_print_value(tfc, key, value, 0, 0);
+}
+
+static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
+{
+    mermaid_print_value(tfc, key, NULL, value, 1);
+}
+
+const AVTextFormatter avtextformatter_mermaid = {
+    .name                 = "mermaid",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
+
+
+const AVTextFormatter avtextformatter_mermaidhtml = {
+    .name                 = "mermaidhtml",
+    .priv_size            = sizeof(MermaidContext),
+    .init                 = mermaid_init_html,
+    .print_section_header = mermaid_print_section_header,
+    .print_section_footer = mermaid_print_section_footer,
+    .print_integer        = mermaid_print_int,
+    .print_string         = mermaid_print_str,
+    .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
+    .priv_class           = &mermaid_class,
+};
diff --git a/fftools/textformat/tf_mermaid.h b/fftools/textformat/tf_mermaid.h
new file mode 100644
index 0000000000..aff73bf9f3
--- /dev/null
+++ b/fftools/textformat/tf_mermaid.h
@@ -0,0 +1,41 @@
+/*
+ * 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_TF_MERMAID_H
+#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
+
+typedef enum {
+    AV_DIAGRAMTYPE_GRAPH,
+    AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
+} AVDiagramType;
+
+typedef struct AVDiagramConfig {
+    AVDiagramType diagram_type;
+    const char *diagram_css;
+    const char *html_template;
+} AVDiagramConfig;
+
+
+void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
+
+void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
+
+
+#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */
\ No newline at end of file
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

* [FFmpeg-devel] [PATCH v7 13/13] fftools/graphprint: Now, make it a Killer-Feature!
  2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
                               ` (11 preceding siblings ...)
  2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 12/13] fftools/graphprint: Add execution graph printing softworkz
@ 2025-04-25 23:31             ` softworkz
  12 siblings, 0 replies; 130+ messages in thread
From: softworkz @ 2025-04-25 23:31 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: softworkz

From: softworkz <softworkz@hotmail.com>

remember this: -sg   <= show-graph

Signed-off-by: softworkz <softworkz@hotmail.com>
---
 doc/ffmpeg.texi              |   4 +
 fftools/Makefile             |   1 +
 fftools/ffmpeg.c             |   2 +-
 fftools/ffmpeg.h             |   1 +
 fftools/ffmpeg_filter.c      |   2 +-
 fftools/ffmpeg_opt.c         |   4 +
 fftools/graph/filelauncher.c | 205 +++++++++++++++++++++++++++++++++++
 fftools/graph/graphprint.c   |  48 +++++++-
 fftools/graph/graphprint.h   |  32 ++++++
 9 files changed, 296 insertions(+), 3 deletions(-)
 create mode 100644 fftools/graph/filelauncher.c

diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 35675b5309..6e9e7aed0e 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1404,6 +1404,10 @@ Writes execution graph details to the specified file in the format set via -prin
 Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
 The default format is json.
 
+@item -sg (@emph{global})
+Writes the execution graph to a temporary html file (mermaidhtml format) and 
+tries to launch it in the default browser.
+
 @item -progress @var{url} (@emph{global})
 Send program-friendly progress information to @var{url}.
 
diff --git a/fftools/Makefile b/fftools/Makefile
index 361a4fd574..56a2910212 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -22,6 +22,7 @@ OBJS-ffmpeg +=                  \
     fftools/ffmpeg_opt.o        \
     fftools/ffmpeg_sched.o      \
     fftools/graph/graphprint.o        \
+    fftools/graph/filelauncher.o      \
     fftools/sync_queue.o        \
     fftools/thread_queue.o      \
     fftools/textformat/avtextformat.o \
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index 6766ec209c..9875a1f7fd 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -309,7 +309,7 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
 static void ffmpeg_cleanup(int ret)
 {
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
 
     if (do_benchmark) {
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7fbf0ad532..49fea0307d 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -721,6 +721,7 @@ extern int print_graphs;
 extern char *print_graphs_file;
 extern char *print_graphs_format;
 extern int auto_conversion_filters;
+extern int show_graph;
 
 extern const AVIOInterruptCB int_cb;
 
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index b774606562..e82e333b7f 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -2985,7 +2985,7 @@ read_frames:
 
 finish:
 
-    if (print_graphs || print_graphs_file)
+    if (print_graphs || print_graphs_file || show_graph)
         print_filtergraph(fg, fgt.graph);
 
     // EOF is normal termination
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3d1efe32f9..24713d640f 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -79,6 +79,7 @@ int vstats_version = 2;
 int print_graphs = 0;
 char *print_graphs_file = NULL;
 char *print_graphs_format = NULL;
+int show_graph = 0;
 int auto_conversion_filters = 1;
 int64_t stats_period = 500000;
 
@@ -1748,6 +1749,9 @@ const OptionDef options[] = {
     { "print_graphs_format", OPT_TYPE_STRING, 0,
         { &print_graphs_format },
       "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
+    { "sg",   OPT_TYPE_BOOL, 0,
+        { &show_graph },
+        "create execution graph as temporary html file and try to launch it in the default browser" },
     { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
         { &auto_conversion_filters },
         "enable automatic conversion filters globally" },
diff --git a/fftools/graph/filelauncher.c b/fftools/graph/filelauncher.c
new file mode 100644
index 0000000000..0cf5f15cf1
--- /dev/null
+++ b/fftools/graph/filelauncher.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2025 - softworkz
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(_WIN32)
+#  include <windows.h>
+#  include <shellapi.h>
+#else
+#  include <sys/time.h>
+#  include <time.h>
+#endif
+#include "graphprint.h"
+
+int ff_open_html_in_browser(const char *html_path)
+{
+    if (!html_path || !*html_path)
+        return -1;
+
+#if defined(_WIN32)
+
+    // --- Windows ---------------------------------
+    {
+        HINSTANCE rc = ShellExecuteA(NULL, "open", html_path, NULL, NULL, SW_SHOWNORMAL);
+        if ((UINT_PTR)rc <= 32) {
+            // Fallback: system("start ...")
+            char cmd[1024];
+            _snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "start \"\" \"%s\"", html_path);
+            if (system(cmd) != 0)
+                return -1;
+        }
+        return 0;
+    }
+
+#elif defined(__APPLE__)
+
+    // --- macOS -----------------------------------
+    {
+        // "open" is the macOS command to open a file/URL with the default application
+        char cmd[1024];
+        snprintf(cmd, sizeof(cmd), "open '%s' 1>/dev/null 2>&1 &", html_path);
+        if (system(cmd) != 0)
+            return -1;
+        return 0;
+    }
+
+#else
+
+    // --- Linux / Unix-like -----------------------
+    // We'll try xdg-open, then gnome-open, then kfmclient
+    {
+        // Helper macro to try one browser command
+        // Returns 0 on success, -1 on failure
+        #define TRY_CMD(prog) do {                                   \
+            char buf[1024];                                          \
+            snprintf(buf, sizeof(buf), "%s '%s' 1>/dev/null 2>&1 &", \
+                     (prog), html_path);                              \
+            int ret = system(buf);                                    \
+            /* On Unix: system() returns -1 if the shell can't run. */\
+            /* Otherwise, check exit code in lower 8 bits.           */\
+            if (ret != -1 && WIFEXITED(ret) && WEXITSTATUS(ret) == 0) \
+                return 0;                                             \
+        } while (0)
+
+        TRY_CMD("xdg-open");
+        TRY_CMD("gnome-open");
+        TRY_CMD("kfmclient exec");
+
+        fprintf(stderr, "Could not open '%s' in a browser.\n", html_path);
+        return -1;
+    }
+
+#endif
+}
+
+
+int ff_get_temp_dir(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    // --- Windows ------------------------------------
+    {
+        // GetTempPathA returns length of the string (including trailing backslash).
+        // If the return value is greater than buffer size, it's an error.
+        DWORD len = GetTempPathA((DWORD)size, buf);
+        if (len == 0 || len > size) {
+            // Could not retrieve or buffer is too small
+            return -1;
+        }
+        return 0;
+    }
+
+#else
+
+    // --- macOS / Linux / Unix -----------------------
+    // Follow typical POSIX convention: check common env variables
+    // and fallback to /tmp if not found.
+    {
+        const char *tmp = getenv("TMPDIR");
+        if (!tmp || !*tmp) tmp = getenv("TMP");
+        if (!tmp || !*tmp) tmp = getenv("TEMP");
+        if (!tmp || !*tmp) tmp = "/tmp";
+
+        // Copy into buf, ensure there's a trailing slash
+        size_t len = strlen(tmp);
+        if (len + 2 > size) {
+            // Need up to len + 1 for slash + 1 for null terminator
+            return -1;
+        }
+
+        strcpy(buf, tmp);
+        // Append slash if necessary
+        if (buf[len - 1] != '/' && buf[len - 1] != '\\') {
+#if defined(__APPLE__)
+            // On macOS/Unix, use forward slash
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#else
+            // Technically on Unix it's always '/', but here's how you'd do if needed:
+            buf[len] = '/';
+            buf[len + 1] = '\0';
+#endif
+        }
+        return 0;
+    }
+
+#endif
+}
+
+int ff_make_timestamped_html_name(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+    /*----------- Windows version -----------*/
+    SYSTEMTIME st;
+    GetLocalTime(&st);
+    /*
+      st.wYear, st.wMonth, st.wDay,
+      st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
+    */
+    int written = _snprintf_s(buf, size, _TRUNCATE,
+                              "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                              st.wYear,
+                              st.wMonth,
+                              st.wDay,
+                              st.wHour,
+                              st.wMinute,
+                              st.wSecond,
+                              st.wMilliseconds);
+    if (written < 0)
+        return -1; /* Could not write into buffer */
+    return 0;
+
+#else
+
+    /*----------- macOS / Linux / Unix version -----------*/
+    struct timeval tv;
+    if (gettimeofday(&tv, NULL) != 0) {
+        return -1; /* gettimeofday failed */
+    }
+
+    struct tm local_tm;
+    localtime_r(&tv.tv_sec, &local_tm);
+
+    int ms = (int)(tv.tv_usec / 1000); /* convert microseconds to milliseconds */
+
+    /* 
+       local_tm.tm_year is years since 1900,
+       local_tm.tm_mon  is 0-based (0=Jan, 11=Dec) 
+    */
+    int written = snprintf(buf, size,
+                           "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+                           local_tm.tm_year + 1900,
+                           local_tm.tm_mon + 1,
+                           local_tm.tm_mday,
+                           local_tm.tm_hour,
+                           local_tm.tm_min,
+                           local_tm.tm_sec,
+                           ms);
+    if (written < 0 || (size_t)written >= size) {
+        return -1; /* Buffer too small or formatting error */
+    }
+    return 0;
+
+#endif
+}
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
index 05c06f80fb..635bae6d5e 100644
--- a/fftools/graph/graphprint.c
+++ b/fftools/graph/graphprint.c
@@ -873,6 +873,11 @@ static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
 
     av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
 
+    if (show_graph) {
+        if (!print_graphs_format || strcmp(print_graphs_format, "mermaidhtml") != 0)
+            print_graphs_format = av_strdup("mermaidhtml");
+    }
+
     if (!print_graphs_format)
         print_graphs_format = av_strdup("json");
     if (!print_graphs_format) {
@@ -1097,5 +1102,46 @@ cleanup:
 
 int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
 {
-    return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+    int ret;
+
+    if (show_graph) {
+        char buf[2048];
+        AVBPrint bp;
+
+        av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+        print_graphs = 0;
+
+        ret = ff_get_temp_dir(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error getting temp directory path for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        ret = ff_make_timestamped_html_name(buf, sizeof(buf));
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Error creating temp file name for graph output file\n");
+            return ret;
+        }
+
+        av_bprint_append_data(&bp, buf, strlen(buf));
+
+        av_bprint_finalize(&bp, &print_graphs_file);
+    }
+
+    ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+    if (!ret && show_graph) {
+        av_log(NULL, AV_LOG_INFO, "Execution graph saved as: %s\n", print_graphs_file);
+        av_log(NULL, AV_LOG_INFO, "Trying to launch graph in browser...\n");
+
+        ret = ff_open_html_in_browser(print_graphs_file);
+        if (ret) {
+            av_log(NULL, AV_LOG_ERROR, "Browser could not be launched for execution graph display\nPlease open manually: %s\n", print_graphs_file);
+        }
+    }
+
+    return ret;
 }
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
index 9f043cc273..43f769870b 100644
--- a/fftools/graph/graphprint.h
+++ b/fftools/graph/graphprint.h
@@ -27,4 +27,36 @@ int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles,
 
 int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
 
+/**
+ * Open an HTML file in the default browser (Windows, macOS, Linux/Unix).
+ *
+ * @param html_path Absolute or relative path to the HTML file.
+ * @return 0 on success, -1 on failure.
+ *
+ * NOTE: This uses system() calls for non-Windows, and ShellExecute on Windows.
+ *       Exercise caution if 'html_path' is untrusted (possible command injection).
+ */
+int ff_open_html_in_browser(const char *html_path);
+
+/**
+ * Retrieve the system's temporary directory.
+ *
+ * @param buf  Output buffer to store the temp directory path (including trailing slash)
+ * @param size Size of the output buffer in bytes
+ * @return 0 on success, -1 on failure (buffer too small or other errors)
+ *
+ * Note: On most platforms, the path will include a trailing slash (e.g. "C:\\Users\\...\\Temp\\" on Windows, "/tmp/" on Unix).
+ */
+int ff_get_temp_dir(char *buf, size_t size);
+
+/**
+ * Create a timestamped HTML filename, e.g.:
+ *   ffmpeg_graph_2024-01-01_22-12-59_123.html
+ *
+ * @param buf  Pointer to buffer where the result is stored
+ * @param size Size of the buffer in bytes
+ * @return 0 on success, -1 on error (e.g. buffer too small)
+ */
+int ff_make_timestamped_html_name(char *buf, size_t size);
+
 #endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
-- 
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".

^ permalink raw reply	[flat|nested] 130+ messages in thread

end of thread, other threads:[~2025-04-25 23:34 UTC | newest]

Thread overview: 130+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-04-14 12:46 [FFmpeg-devel] [PATCH 0/9] Execution Graph Printing ffmpegagent
2025-04-14 12:46 ` [FFmpeg-devel] [PATCH 1/9] fftools/textformat: Formatting and whitespace changes softworkz
2025-04-21 16:52   ` Stefano Sabatini
2025-04-21 17:12     ` softworkz .
2025-04-14 12:46 ` [FFmpeg-devel] [PATCH 2/9] fftools/textformat: Quality improvements softworkz
2025-04-15  1:05   ` Andreas Rheinhardt
2025-04-15  3:19     ` softworkz .
2025-04-16  4:50       ` Andreas Rheinhardt
2025-04-16  6:27         ` softworkz .
2025-04-16  9:52     ` softworkz .
2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 3/9] fftools/textformat: Introduce common header and deduplicate code softworkz
2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 4/9] fftools/textformat: Add function avtext_print_integer_flags() softworkz
2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 5/9] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 6/9] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 7/9] fftools/resources: Add resource manager files softworkz
2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 8/9] fftools/graphprint: Add execution graph printing softworkz
2025-04-14 12:47 ` [FFmpeg-devel] [PATCH 9/9] fftools/graphprint: Now, make it a Killer-Feature! softworkz
2025-04-16 10:12 ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing ffmpegagent
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 01/10] fftools/textformat: Formatting and whitespace changes softworkz
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 02/10] fftools/textformat: Quality improvements softworkz
2025-04-16 10:49     ` Andreas Rheinhardt
2025-04-16 11:33       ` softworkz .
2025-04-18  2:48         ` softworkz .
2025-04-18  5:41         ` softworkz .
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 03/10] fftools/textformat: Introduce common header and deduplicate code softworkz
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 04/10] fftools/tf_internal: Use ac_default_item_name softworkz
2025-04-16 10:50     ` Andreas Rheinhardt
2025-04-16 11:11       ` softworkz .
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 05/10] fftools/textformat: Add function avtext_print_integer_flags() softworkz
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 06/10] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 07/10] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 08/10] fftools/resources: Add resource manager files softworkz
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 09/10] fftools/graphprint: Add execution graph printing softworkz
2025-04-17 18:41     ` Michael Niedermayer
2025-04-18  2:45       ` softworkz .
2025-04-16 10:12   ` [FFmpeg-devel] [PATCH v2 10/10] fftools/graphprint: Now, make it a Killer-Feature! softworkz
2025-04-16 10:21   ` [FFmpeg-devel] [PATCH v2 00/10] Execution Graph Printing softworkz .
2025-04-18  2:56   ` [FFmpeg-devel] [PATCH v3 00/11] " ffmpegagent
2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 01/11] fftools/textformat: Formatting and whitespace changes softworkz
2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 02/11] fftools/textformat: Quality improvements softworkz
2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 03/11] fftools/textformat: Introduce common header and deduplicate code softworkz
2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 04/11] fftools/tf_internal: Use ac_default_item_name softworkz
2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 05/11] fftools/textformat: Add function avtext_print_integer_flags() softworkz
2025-04-18  2:56     ` [FFmpeg-devel] [PATCH v3 06/11] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 07/11] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 08/11] fftools/resources: Add resource manager files softworkz
2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 09/11] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 10/11] fftools/graphprint: Add execution graph printing softworkz
2025-04-18  2:57     ` [FFmpeg-devel] [PATCH v3 11/11] fftools/graphprint: Now, make it a Killer-Feature! softworkz
2025-04-20 10:11       ` Michael Niedermayer
2025-04-20 22:59     ` [FFmpeg-devel] [PATCH v4 00/11] Execution Graph Printing ffmpegagent
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 01/11] fftools/textformat: Formatting and whitespace changes softworkz
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 02/11] fftools/textformat: Quality improvements softworkz
2025-04-21 17:16         ` Stefano Sabatini
2025-04-21 17:21           ` Nicolas George
2025-04-21 17:40             ` softworkz .
2025-04-21 17:29           ` softworkz .
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 03/11] fftools/textformat: Introduce common header and deduplicate code softworkz
2025-04-21 17:28         ` Stefano Sabatini
2025-04-21 17:31           ` softworkz .
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 04/11] fftools/tf_internal: Use ac_default_item_name softworkz
2025-04-21 17:31         ` Stefano Sabatini
2025-04-22 21:10           ` softworkz .
2025-04-23 22:36             ` softworkz .
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 05/11] fftools/textformat: Add function avtext_print_integer_flags() softworkz
2025-04-23 22:56         ` Stefano Sabatini
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 06/11] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 07/11] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 08/11] fftools/resources: Add resource manager files softworkz
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 09/11] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 10/11] fftools/graphprint: Add execution graph printing softworkz
2025-04-20 22:59       ` [FFmpeg-devel] [PATCH v4 11/11] fftools/graphprint: Now, make it a Killer-Feature! softworkz
2025-04-22 21:55       ` [FFmpeg-devel] [PATCH v5 00/14] Execution Graph Printing ffmpegagent
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 01/14] fftools/textformat: Formatting and whitespace changes softworkz
2025-04-23 22:08           ` Stefano Sabatini
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 02/14] fftools/textformat: Apply quality improvements softworkz
2025-04-23 22:34           ` Stefano Sabatini
2025-04-23 22:53             ` softworkz .
2025-04-23 22:56               ` Nicolas George
2025-04-23 23:04                 ` softworkz .
2025-04-23 23:16                   ` softworkz .
2025-04-23 23:54             ` softworkz .
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 03/14] fftools/avtextformat: Re-use BPrint in loop softworkz
2025-04-23 22:45           ` Stefano Sabatini
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 04/14] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
2025-04-23 22:48           ` Stefano Sabatini
2025-04-23 22:55             ` softworkz .
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 05/14] fftools/textformat: Introduce common header and deduplicate code softworkz
2025-04-23 22:49           ` Stefano Sabatini
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 06/14] fftools/textformat: AVTextWriter change writer_printf signature softworkz
2025-04-23 23:07           ` Stefano Sabatini
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 07/14] fftools/tf_internal: Use av_default_item_name softworkz
2025-04-23 22:57           ` Stefano Sabatini
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 08/14] fftools/textformat: Add function avtext_print_integer_flags() softworkz
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 09/14] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 10/14] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 11/14] fftools/resources: Add resource manager files softworkz
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 12/14] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 13/14] fftools/graphprint: Add execution graph printing softworkz
2025-04-22 21:55         ` [FFmpeg-devel] [PATCH v5 14/14] fftools/graphprint: Now, make it a Killer-Feature! softworkz
2025-04-24  1:12         ` [FFmpeg-devel] [PATCH v6 00/13] Execution Graph Printing ffmpegagent
2025-04-24  1:12           ` [FFmpeg-devel] [PATCH v6 01/13] fftools/textformat: Formatting and whitespace changes softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 02/13] fftools/textformat: Apply quality improvements softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 03/13] fftools/avtextformat: Re-use BPrint in loop softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 04/13] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 05/13] fftools/textformat: Introduce common header and deduplicate code softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 06/13] fftools/tf_internal: Use av_default_item_name softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 07/13] fftools/textformat: Add function avtext_print_integer_flags() softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 08/13] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 09/13] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 10/13] fftools/resources: Add resource manager files softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 11/13] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 12/13] fftools/graphprint: Add execution graph printing softworkz
2025-04-25 22:26             ` Michael Niedermayer
2025-04-25 23:17               ` softworkz .
2025-04-24  1:13           ` [FFmpeg-devel] [PATCH v6 13/13] fftools/graphprint: Now, make it a Killer-Feature! softworkz
2025-04-25 23:30           ` [FFmpeg-devel] [PATCH v7 00/13] Execution Graph Printing ffmpegagent
2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 01/13] fftools/textformat: Formatting and whitespace changes softworkz
2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 02/13] fftools/textformat: Apply quality improvements softworkz
2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 03/13] fftools/avtextformat: Re-use BPrint in loop softworkz
2025-04-25 23:30             ` [FFmpeg-devel] [PATCH v7 04/13] fftools/textformat: Introduce AVTextFormatOptions for avtext_context_open() softworkz
2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 05/13] fftools/textformat: Introduce common header and deduplicate code softworkz
2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 06/13] fftools/tf_internal: Use av_default_item_name softworkz
2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 07/13] fftools/textformat: Add function avtext_print_integer_flags() softworkz
2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 08/13] fftools/ffmpeg_filter: Move some declaration to new header file softworkz
2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 09/13] avfilter/avfilter: Add avfilter_link_get_hw_frames_ctx() softworkz
2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 10/13] fftools/resources: Add resource manager files softworkz
2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 11/13] fftools/ffmpeg_mux: Make ms_from_ost() inline softworkz
2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 12/13] fftools/graphprint: Add execution graph printing softworkz
2025-04-25 23:31             ` [FFmpeg-devel] [PATCH v7 13/13] fftools/graphprint: Now, make it a Killer-Feature! softworkz

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