PR #24945 opened by Kacper Michajłow (kasper93)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24945
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24945.patch


From c778e33463ea1d6af9b37f42c70a035971bb048a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 8 Oct 2026 05:18:37 +0200
Subject: [PATCH 1/5] fate: add compact output tests for program streams,
 timecode rows and printed graphs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Kacper Michajłow <[email protected]>
---
 tests/fate/ffmpeg.mak                       |  3 +++
 tests/fate/h264.mak                         |  3 +++
 tests/fate/mpegts.mak                       |  6 ++++++
 tests/ref/fate/ffmpeg-print-graphs-compact  | 20 ++++++++++++++++++++
 tests/ref/fate/h264-timecode-probe          |  8 ++++++++
 tests/ref/fate/mpegts-probe-program-compact |  4 ++++
 6 files changed, 44 insertions(+)
 create mode 100644 tests/ref/fate/ffmpeg-print-graphs-compact
 create mode 100644 tests/ref/fate/h264-timecode-probe
 create mode 100644 tests/ref/fate/mpegts-probe-program-compact

diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak
index b749af4a7e..1314635af1 100644
--- a/tests/fate/ffmpeg.mak
+++ b/tests/fate/ffmpeg.mak
@@ -19,6 +19,9 @@ fate-ffmpeg-lavfi: CMD = framecrc -lavfi color=d=1:r=5 
-fflags +bitexact
 FATE_FFMPEG-$(call FILTERFRAMECRC, TESTSRC2 FORMAT SETPARAMS SCALE, 
LAVFI_INDEV WRAPPED_AVFRAME_DECODER MPEG2VIDEO_ENCODER) += 
fate-ffmpeg-chroma-location
 fate-ffmpeg-chroma-location: CMD = framecrc -auto_conversion_filters -f lavfi 
-i testsrc2=s=64x64:r=25:d=1,format=yuv420p,setparams=chroma_location=topleft 
-sws_flags +accurate_rnd+bitexact -flags +bitexact -idct simple -dct fastint 
-threads 1 -c:v mpeg2video -qscale:v 2 -frames:v 1
 
+FATE_FFMPEG-$(call ALLYES, TESTSRC2_FILTER SCALE_FILTER FORMAT_FILTER 
LAVFI_INDEV WRAPPED_AVFRAME_DECODER RAWVIDEO_ENCODER NULL_MUXER) += 
fate-ffmpeg-print-graphs-compact
+fate-ffmpeg-print-graphs-compact: CMD = ffmpeg -f lavfi -i 
testsrc2=s=64x64:r=25:d=0.08 -filter_complex 
"[0:v]scale=32:32,format=yuv420p[v]" -map "[v]" -c:v rawvideo 
-print_graphs_file - -print_graphs_format compact -f null -
+
 FATE_FFMPEG-$(call ENCDEC2, MPEG4, RAWVIDEO, AVI, RAWVIDEO_DEMUXER 
FRAMECRC_MUXER) += fate-force_key_frames
 fate-force_key_frames: tests/data/vsynth1.yuv
 fate-force_key_frames: CMD = enc_dec \
diff --git a/tests/fate/h264.mak b/tests/fate/h264.mak
index 0fec104bfc..1ee70f216d 100644
--- a/tests/fate/h264.mak
+++ b/tests/fate/h264.mak
@@ -254,6 +254,7 @@ FATE_H264_FFPROBE-$(call DEMDEC, MATROSKA, H264) += 
fate-h264-dts_5frames
 FATE_H264_FFPROBE-$(call DEMDEC, MATROSKA, H264) += fate-h264-skip-pred-pts
 FATE_H264_FFPROBE-$(call DEMDEC, MOV, H264) += fate-h264-late-sei
 FATE_H264_FFPROBE-$(call PARSERDEMDEC, H264, H264, H264) += fate-h264-afd
+FATE_H264_FFPROBE-$(call PARSERDEMDEC, H264, H264, H264) += 
fate-h264-timecode-probe
 FATE_H264_FFPROBE-$(call PARSERDEMDEC, H264, H264, H264) += 
fate-h264-skip-pred \
                                                             
fate-h264-skip-pred-fields
 
@@ -502,6 +503,8 @@ fate-h264-skip-pred-fields:                       CMD = 
probeframes -show_entrie
 fate-h264-skip-pred-pts:                          CMD = probeframes 
-show_entries frame=key_frame,pts,pict_type \
                                                         -skip_pred all 
-skip_idct all $(TARGET_SAMPLES)/h264/dts_5frames.mkv
 fate-h264-late-sei:                               CMD = probeframes -of 
compact -show_entries frame=color_transfer $(TARGET_SAMPLES)/h264/late-sei.mp4
+fate-h264-timecode-probe:                         CMD = probe -of compact 
-show_entries frame=key_frame,pict_type:frame_side_data_list \
+                                                        -read_intervals 
"%+\#2" $(TARGET_SAMPLES)/h264/crew_cif_timecode-2.h264
 fate-h264-afd:                                    CMD = run 
ffprobe$(PROGSSUF)$(EXESUF) -bitexact -apply_cropping 0 \
                                                         -show_entries 
frame=width,height,crop_top,crop_bottom,crop_left,crop_right:frame_side_data_list:stream=width,height,coded_width,coded_height
 \
                                                         
$(TARGET_SAMPLES)/h264/bbc2.sample.h264
diff --git a/tests/fate/mpegts.mak b/tests/fate/mpegts.mak
index 5943571de9..39600294df 100644
--- a/tests/fate/mpegts.mak
+++ b/tests/fate/mpegts.mak
@@ -29,6 +29,12 @@ FATE_MPEGTS_PROBE-$(call DEMDEC, MPEGTS) += 
fate-mpegts-probe-pmt-merge
 fate-mpegts-probe-pmt-merge: SRC = $(TARGET_SAMPLES)/mpegts/pmtchange.ts
 fate-mpegts-probe-pmt-merge: CMD = run $(PROBE_CODEC_NAME_COMMAND) 
-merge_pmt_versions 1 -i "$(SRC)"
 
+FATE_MPEGTS_PROBE-$(call DEMDEC, MPEGTS, HEVC, LOAS_DEMUXER) += 
fate-mpegts-probe-program-compact
+fate-mpegts-probe-program-compact: SRC = $(TARGET_SAMPLES)/mpegts/loewe.ts
+fate-mpegts-probe-program-compact: CMD = run ffprobe$(PROGSSUF)$(EXESUF) -v 0 
-bitexact -of compact \
+    -show_entries 
packet=stream_index,pts:packet_side_data_list:program=program_id,nb_streams:program_stream=index,codec_name
 \
+    -read_intervals "%+\#2" -i "$(SRC)"
+
 FATE_MPEGTS_FFMPEG_FFPROBE-$(call FRAMECRC, MOV, H264, H264_PARSER 
LCEVC_PARSER) += fate-mpegts-lcevc-h264-single-track
 fate-mpegts-lcevc-h264-single-track: CMD = stream_demux mpegts 
$(TARGET_SAMPLES)/lcevc/L_H264_640x360p_8bit8bit_2D_dd.ts \
   "" "-c:v copy" \
diff --git a/tests/ref/fate/ffmpeg-print-graphs-compact 
b/tests/ref/fate/ffmpeg-print-graphs-compact
new file mode 100644
index 0000000000..a5b136efeb
--- /dev/null
+++ b/tests/ref/fate/ffmpeg-print-graphs-compact
@@ -0,0 +1,20 @@
+graph|graph_index=0|name=Graph 
0.0|id=Graph_0_0|description=[0:v]scale=32:32,format=yuv420p[v]
+graph_input|input_index=0|link_label=0:v|filter_id=G0_graph_0_input_from_stream_0_0|filter_name=buffer|media_type=video
+graph_output|output_index=0|name=#0:0|filter_id=G0_out__0_0|filter_name=buffersink|media_type=video
+filter|filter_name=scale|description=Scale the input video size and/or convert 
the image format.|nb_inputs=1|nb_outputs=1
+filter_input|input_index=0|pad_name=default|source_filter_id=in_0_v|source_pad_name=default|filter_id=G0_Parsed_scale_0|media_type=video|format=yuv420p|width=64|height=64|sar=1:1|sample_rate=1/25
+filter_output|dest_filter_id=G0_Parsed_format_1|output_index=0|pad_name=default|dest_pad_name=default|filter_id=G0_Parsed_scale_0|media_type=video|format=yuv420p|width=32|height=32|sar=1:1|sample_rate=1/25
+filter|filter_name=format|description=Convert the input video to one of the 
specified pixel formats.|nb_inputs=1|nb_outputs=1
+filter_input|input_index=0|pad_name=default|source_filter_id=G0_Parsed_scale_0|source_pad_name=default|filter_id=G0_Parsed_format_1|media_type=video|format=yuv420p|width=32|height=32|sar=1:1|sample_rate=1/25
+filter_output|dest_filter_id=out__0_0|output_index=0|pad_name=default|dest_pad_name=default|filter_id=G0_Parsed_format_1|media_type=video|format=yuv420p|width=32|height=32|sar=1:1|sample_rate=1/25
+filter|filter_name=buffer|description=Buffer video frames, and make them 
accessible to the filterchain.|nb_inputs=0|nb_outputs=1
+filter_output|dest_filter_id=G0_Parsed_scale_0|output_index=0|pad_name=default|dest_pad_name=default|filter_id=G0_graph_0_input_from_stream_0_0|media_type=video|format=yuv420p|width=64|height=64|sar=1:1|sample_rate=1/25
+filter|filter_name=buffersink|description=Buffer video frames, and make them 
available to the end of the filter graph.|nb_inputs=1|nb_outputs=0
+filter_input|input_index=0|pad_name=default|source_filter_id=G0_Parsed_format_1|source_pad_name=default|filter_id=G0_out__0_0|media_type=video|format=yuv420p|width=32|height=32|sar=1:1|sample_rate=1/25
+
+inputfile|index=0|demuxer_name=lavfi|file_extension=08|url=testsrc2=s=64x64:r=25:d=0.08
+inputstream|id=r_in_0_0|name=AVFrame to AVPacket 
passthrough|index=0|media_type=video
+decoder|source_id=r_in_0_0|id=in_0_0|name=wrapped_avframe|media_type=video
+encoder|id=out__0_0|dest_id=r_out__0_0|name=rawvideo|media_type=video
+outputfile|index=0|muxer_name=null|url=pipe:
+outputstream|id=r_out__0_0|name=raw video|index=0|media_type=video
diff --git a/tests/ref/fate/h264-timecode-probe 
b/tests/ref/fate/h264-timecode-probe
new file mode 100644
index 0000000000..dffe7ae762
--- /dev/null
+++ b/tests/ref/fate/h264-timecode-probe
@@ -0,0 +1,8 @@
+frame|key_frame=1|pict_type=I|side_datum/h_26_45__user_data_unregistered_sei_message:side_data_type=H.26[45]
 User Data Unregistered SEI 
message|side_datum/smpte_12_1_timecode:side_data_type=SMPTE 12-1 timecode
+timecode|value=00:00:00:00
+timecode|value=00:00:00:00
+
+frame|key_frame=0|pict_type=P|side_datum/smpte_12_1_timecode:side_data_type=SMPTE
 12-1 timecode
+timecode|value=00:00:00:02
+timecode|value=00:00:00:02
+
diff --git a/tests/ref/fate/mpegts-probe-program-compact 
b/tests/ref/fate/mpegts-probe-program-compact
new file mode 100644
index 0000000000..bd884a0289
--- /dev/null
+++ b/tests/ref/fate/mpegts-probe-program-compact
@@ -0,0 +1,4 @@
+packet|stream_index=0|pts=2863340144|side_datum/mpegts_stream_id:side_data_type=MPEGTS
 Stream ID|side_datum/mpegts_stream_id:id=224
+packet|stream_index=0|pts=2863338344|side_datum/mpegts_stream_id:side_data_type=MPEGTS
 Stream ID|side_datum/mpegts_stream_id:id=224
+program|program_id=769|nb_streams=2
+stream|index=0|codec_name=hevcstream|index=1|codec_name=aac_latm
\ No newline at end of file
-- 
2.52.0


From 23469dd568abbb1f865cae1d9884f586e8079866 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 8 Oct 2026 03:38:24 +0200
Subject: [PATCH 2/5] fftools/graph/graphprint: record and replay the print
 calls of a graph
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Each graph prints itself from its own thread, and splicing the text that
a separate formatter context wrote for the graph section into the final
one only works for writers that print every field immediately. Record
the calls instead and replay them into the context that prints
everything at the end, so the writers see one consistent sequence. This
removes the blank line after each graph in compact and csv output and
numbers the graphs correctly in flat and ini output.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 fftools/graph/graphprint.c                 | 428 ++++++++++++++-------
 tests/ref/fate/ffmpeg-print-graphs-compact |   1 -
 2 files changed, 299 insertions(+), 130 deletions(-)

diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
index 9ffeca3f11..f069f6de71 100644
--- a/fftools/graph/graphprint.c
+++ b/fftools/graph/graphprint.c
@@ -124,35 +124,230 @@ typedef struct GraphPrintContext {
     AVTextFormatContext *tfc;
     AVTextWriterContext *wctx;
     AVDiagramConfig diagram_config;
+    /** record of the print calls, used instead of a formatter context */
+    AVBPrint *rec;
 
     int id_prefix_num;
     int is_diagram;
     int opt_flags;
+    int show_value_unit;
     int skip_buffer_filters;
     AVBPrint pbuf;
 
 } GraphPrintContext;
 
+enum GraphPrintOp {
+    GP_OP_SECTION_HEADER,
+    GP_OP_SECTION_FOOTER,
+    GP_OP_STRING,
+    GP_OP_INTEGER,
+    GP_OP_UNIT_INTEGER,
+};
+
+static void rec_write(AVBPrint *rec, const void *data, size_t size)
+{
+    av_bprint_append_data(rec, data, size);
+}
+
+static void rec_write_str(AVBPrint *rec, const char *str)
+{
+    uint32_t len = str ? strlen(str) + 1 : 0;
+
+    rec_write(rec, &len, sizeof(len));
+    if (len)
+        rec_write(rec, str, len);
+}
+
+static int rec_read(const uint8_t **p, const uint8_t *end, void *data, size_t 
size)
+{
+    if ((size_t)(end - *p) < size)
+        return AVERROR_INVALIDDATA;
+    memcpy(data, *p, size);
+    *p += size;
+    return 0;
+}
+
+static int rec_read_str(const uint8_t **p, const uint8_t *end, const char 
**str)
+{
+    uint32_t len;
+    int ret = rec_read(p, end, &len, sizeof(len));
+
+    if (ret < 0)
+        return ret;
+    if ((size_t)(end - *p) < len || (len && (*p)[len - 1]))
+        return AVERROR_INVALIDDATA;
+    *str = len ? (const char *)*p : NULL;
+    *p += len;
+    return 0;
+}
+
+static void gp_section_header(const GraphPrintContext *gpc, const 
AVTextFormatSectionContext *sec_ctx, int section_id)
+{
+    if (gpc->rec) {
+        uint8_t op = GP_OP_SECTION_HEADER, has_ctx = !!sec_ctx;
+        int32_t id = section_id;
+
+        rec_write(gpc->rec, &op, sizeof(op));
+        rec_write(gpc->rec, &id, sizeof(id));
+        rec_write(gpc->rec, &has_ctx, sizeof(has_ctx));
+        if (sec_ctx) {
+            int32_t flags = sec_ctx->context_flags;
+
+            rec_write_str(gpc->rec, sec_ctx->context_id);
+            rec_write_str(gpc->rec, sec_ctx->context_type);
+            rec_write(gpc->rec, &flags, sizeof(flags));
+        }
+        return;
+    }
+    avtext_print_section_header(gpc->tfc, sec_ctx, section_id);
+}
+
+static void gp_section_footer(const GraphPrintContext *gpc)
+{
+    if (gpc->rec) {
+        uint8_t op = GP_OP_SECTION_FOOTER;
+
+        rec_write(gpc->rec, &op, sizeof(op));
+        return;
+    }
+    avtext_print_section_footer(gpc->tfc);
+}
+
+static void gp_print_string(const GraphPrintContext *gpc, const char *key, 
const char *value, int flags)
+{
+    if (gpc->rec) {
+        uint8_t op = GP_OP_STRING;
+        int32_t f = flags;
+
+        rec_write(gpc->rec, &op, sizeof(op));
+        rec_write(gpc->rec, &f, sizeof(f));
+        rec_write_str(gpc->rec, key);
+        rec_write_str(gpc->rec, value);
+        return;
+    }
+    avtext_print_string(gpc->tfc, key, value, flags);
+}
+
+static void gp_print_integer(const GraphPrintContext *gpc, const char *key, 
int64_t value, int flags)
+{
+    if (gpc->rec) {
+        uint8_t op = GP_OP_INTEGER;
+        int32_t f = flags;
+
+        rec_write(gpc->rec, &op, sizeof(op));
+        rec_write(gpc->rec, &f, sizeof(f));
+        rec_write_str(gpc->rec, key);
+        rec_write(gpc->rec, &value, sizeof(value));
+        return;
+    }
+    avtext_print_integer(gpc->tfc, key, value, flags);
+}
+
+static void gp_print_unit_integer(const GraphPrintContext *gpc, const char 
*key, int64_t value, const char *unit)
+{
+    if (gpc->rec) {
+        uint8_t op = GP_OP_UNIT_INTEGER;
+
+        rec_write(gpc->rec, &op, sizeof(op));
+        rec_write_str(gpc->rec, key);
+        rec_write(gpc->rec, &value, sizeof(value));
+        rec_write_str(gpc->rec, unit);
+        return;
+    }
+    avtext_print_unit_integer(gpc->tfc, key, value, 
AV_TEXTFORMAT_VALUE_FMT_INT, unit);
+}
+
+static void gp_print_rational(const GraphPrintContext *gpc, const char *key, 
AVRational q, char sep)
+{
+    char buf[44];
+
+    snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
+    gp_print_string(gpc, key, buf, 0);
+}
+
+static int gp_replay(const GraphPrintContext *gpc, const AVBPrint *rec)
+{
+    const uint8_t *p = (const uint8_t *)rec->str;
+    const uint8_t *end = p + FFMIN(rec->len, rec->size - 1);
+    int ret;
+
+    while (p < end) {
+        AVTextFormatSectionContext sec_ctx = { 0 };
+        const char *key, *str;
+        uint8_t op, has_ctx;
+        int32_t id, flags;
+        int64_t value;
+
+        if ((ret = rec_read(&p, end, &op, sizeof(op))) < 0)
+            return ret;
+
+        switch (op) {
+        case GP_OP_SECTION_HEADER:
+            if ((ret = rec_read(&p, end, &id, sizeof(id))) < 0 ||
+                (ret = rec_read(&p, end, &has_ctx, sizeof(has_ctx))) < 0)
+                return ret;
+            if (has_ctx) {
+                if ((ret = rec_read_str(&p, end, &str)) < 0 ||
+                    (ret = rec_read_str(&p, end, &sec_ctx.context_type)) < 0 ||
+                    (ret = rec_read(&p, end, &flags, sizeof(flags))) < 0)
+                    return ret;
+                sec_ctx.context_id    = (char *)str;
+                sec_ctx.context_flags = flags;
+            }
+            avtext_print_section_header(gpc->tfc, has_ctx ? &sec_ctx : NULL, 
id);
+            break;
+        case GP_OP_SECTION_FOOTER:
+            avtext_print_section_footer(gpc->tfc);
+            break;
+        case GP_OP_STRING:
+            if ((ret = rec_read(&p, end, &flags, sizeof(flags))) < 0 ||
+                (ret = rec_read_str(&p, end, &key)) < 0 ||
+                (ret = rec_read_str(&p, end, &str)) < 0)
+                return ret;
+            avtext_print_string(gpc->tfc, key, str, flags);
+            break;
+        case GP_OP_INTEGER:
+            if ((ret = rec_read(&p, end, &flags, sizeof(flags))) < 0 ||
+                (ret = rec_read_str(&p, end, &key)) < 0 ||
+                (ret = rec_read(&p, end, &value, sizeof(value))) < 0)
+                return ret;
+            avtext_print_integer(gpc->tfc, key, value, flags);
+            break;
+        case GP_OP_UNIT_INTEGER:
+            if ((ret = rec_read_str(&p, end, &key)) < 0 ||
+                (ret = rec_read(&p, end, &value, sizeof(value))) < 0 ||
+                (ret = rec_read_str(&p, end, &str)) < 0)
+                return ret;
+            avtext_print_unit_integer(gpc->tfc, key, value, 
AV_TEXTFORMAT_VALUE_FMT_INT, str);
+            break;
+        default:
+            return AVERROR_INVALIDDATA;
+        }
+    }
+
+    return 0;
+}
+
 /* 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, 0)
-#define print_int_opt(k, v)     avtext_print_integer(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_integer(tfc, k, v, 
AV_TEXTFORMAT_VALUE_FMT_INT, u)
+#define print_int(k, v)         gp_print_integer(gpc, k, v, 0)
+#define print_int_opt(k, v)     gp_print_integer(gpc, k, v, gpc->opt_flags)
+#define print_q(k, v, s)        gp_print_rational(gpc, k, v, s)
+#define print_str(k, v)         gp_print_string(gpc, k, v, 0)
+#define print_str_opt(k, v)     gp_print_string(gpc, k, v, gpc->opt_flags)
+#define print_val(k, v, u)      gp_print_unit_integer(gpc, 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);    \
+    gp_print_string(gpc, 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);    \
+    gp_print_string(gpc, k, gpc->pbuf.str, gpc->opt_flags);    \
 } while (0)
 
 
@@ -201,7 +396,6 @@ static char *get_extension(const char *url)
 
 static void print_hwdevicecontext(const GraphPrintContext *gpc, const 
AVHWDeviceContext *hw_device_context)
 {
-    AVTextFormatContext *tfc = gpc->tfc;
 
     if (!hw_device_context)
         return;
@@ -212,14 +406,13 @@ static void print_hwdevicecontext(const GraphPrintContext 
*gpc, const AVHWDevice
 
 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);
+    gp_section_header(gpc, 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));
@@ -242,12 +435,11 @@ static void print_hwframescontext(const GraphPrintContext 
*gpc, const AVHWFrames
     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
+    gp_section_footer(gpc); // SECTION_ID_HWFRAMESCONTEXT
 }
 
 static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
 {
-    AVTextFormatContext *tfc = gpc->tfc;
     AVBufferRef *hw_frames_ctx;
     char layout_string[64];
 
@@ -272,7 +464,7 @@ static void print_link(GraphPrintContext *gpc, AVFilterLink 
*link)
         }
 
         if (link->w && link->h) {
-            if (tfc->opts.show_value_unit) {
+            if (gpc->show_value_unit) {
                 print_fmt("size", "%dx%d", link->w, link->h);
             } else {
                 print_int("width", link->w);
@@ -293,7 +485,7 @@ static void print_link(GraphPrintContext *gpc, AVFilterLink 
*link)
         ////print_str("format", 
av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
 
         if (link->w && link->h) {
-            if (tfc->opts.show_value_unit) {
+            if (gpc->show_value_unit) {
                 print_fmt("size", "%dx%d", link->w, link->h);
             } else {
                 print_int("width", link->w);
@@ -307,7 +499,7 @@ static void print_link(GraphPrintContext *gpc, AVFilterLink 
*link)
         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->opts.show_value_unit)
+        if (gpc->show_value_unit)
             print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
         else
             print_val("sample_rate", link->sample_rate, "Hz");
@@ -331,7 +523,6 @@ static char sanitize_char(const char c)
 
 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)
@@ -353,7 +544,6 @@ static void print_sanizied_id(const GraphPrintContext *gpc, 
const char *key, con
 
 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;
 
@@ -371,7 +561,7 @@ static void print_section_header_id(const GraphPrintContext 
*gpc, int section_id
 
     sec_ctx.context_id = buf.str;
 
-    avtext_print_section_header(tfc, &sec_ctx, section_id);
+    gp_section_header(gpc, &sec_ctx, section_id);
 
     av_bprint_finalize(&buf, NULL);
 }
@@ -383,7 +573,6 @@ static const char *get_filterpad_name(const AVFilterPad 
*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);
@@ -404,14 +593,14 @@ static void print_filter(GraphPrintContext *gpc, const 
AVFilterContext *filter,
             print_int("extra_hw_frames", filter->extra_hw_frames);
     }
 
-    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
+    gp_section_header(gpc, 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);
+        gp_section_header(gpc, &sec_ctx, SECTION_ID_FILTER_INPUT);
         sec_ctx.context_type = NULL;
 
         print_int_opt("input_index", i);
@@ -431,12 +620,12 @@ static void print_filter(GraphPrintContext *gpc, const 
AVFilterContext *filter,
 
         print_link(gpc, link);
 
-        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
+        gp_section_footer(gpc); // SECTION_ID_FILTER_INPUT
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
+    gp_section_footer(gpc); // SECTION_ID_FILTER_INPUTS
 
-    avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
+    gp_section_header(gpc, NULL, SECTION_ID_FILTER_OUTPUTS);
 
     for (unsigned i = 0; i < filter->nb_outputs; i++) {
         AVDictionaryEntry *dic_entry;
@@ -444,7 +633,7 @@ static void print_filter(GraphPrintContext *gpc, const 
AVFilterContext *filter,
         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);
+        gp_section_header(gpc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
         sec_ctx.context_type = NULL;
 
         dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
@@ -463,17 +652,16 @@ static void print_filter(GraphPrintContext *gpc, const 
AVFilterContext *filter,
 
         print_link(gpc, link);
 
-        avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
+        gp_section_footer(gpc); // SECTION_ID_FILTER_OUTPUT
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
+    gp_section_footer(gpc); // SECTION_ID_FILTER_OUTPUTS
 
-    avtext_print_section_footer(tfc); // SECTION_ID_FILTER
+    gp_section_footer(gpc); // SECTION_ID_FILTER
 }
 
 static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, 
AVFilterGraph *graph)
 {
-    AVTextFormatContext *tfc = gpc->tfc;
     AVDictionary *input_map = NULL;
     AVDictionary *output_map = NULL;
 
@@ -488,7 +676,7 @@ static void print_filtergraph_single(GraphPrintContext 
*gpc, FilterGraph *fg, AV
         InputFilter *ifilter = fg->inputs[i];
         enum AVMediaType media_type = ifilter->type;
 
-        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
+        gp_section_header(gpc, NULL, SECTION_ID_GRAPH_INPUT);
 
         print_int("input_index", ifilter->index);
 
@@ -507,17 +695,17 @@ static void print_filtergraph_single(GraphPrintContext 
*gpc, FilterGraph *fg, AV
 
         print_str("media_type", av_get_media_type_string(media_type));
 
-        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
+        gp_section_footer(gpc); // SECTION_ID_GRAPH_INPUT
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
+    gp_section_footer(gpc); // 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++) {
         OutputFilter *ofilter = fg->outputs[i];
 
-        avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
+        gp_section_header(gpc, NULL, SECTION_ID_GRAPH_OUTPUT);
 
         print_int("output_index", ofilter->index);
 
@@ -537,17 +725,17 @@ static void print_filtergraph_single(GraphPrintContext 
*gpc, FilterGraph *fg, AV
 
         print_str("media_type", av_get_media_type_string(ofilter->type));
 
-        avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
+        gp_section_footer(gpc); // SECTION_ID_GRAPH_OUTPUT
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
+    gp_section_footer(gpc); // 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);
+        gp_section_header(gpc, &sec_ctx, SECTION_ID_FILTERS);
 
         if (gpc->is_diagram) {
             print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
@@ -572,7 +760,7 @@ static void print_filtergraph_single(GraphPrintContext 
*gpc, FilterGraph *fg, AV
             print_filter(gpc, filter, input_map, output_map);
         }
 
-        avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
+        gp_section_footer(gpc); // SECTION_ID_FILTERS
     }
 
     // Clean up dictionaries
@@ -582,7 +770,6 @@ static void print_filtergraph_single(GraphPrintContext 
*gpc, FilterGraph *fg, AV
 
 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 };
 
@@ -595,7 +782,7 @@ static int print_streams(GraphPrintContext *gpc, InputFile 
**ifiles, int nb_ifil
         AVFormatContext *fc = ifi->ctx;
 
         sec_ctx.context_id = av_asprintf("Input_%d", n);
-        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
+        gp_section_header(gpc, &sec_ctx, SECTION_ID_INPUTFILE);
         av_freep(&sec_ctx.context_id);
 
         print_fmt("index", "%d", ifi->index);
@@ -614,7 +801,7 @@ static int print_streams(GraphPrintContext *gpc, InputFile 
**ifiles, int nb_ifil
 
         sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
 
-        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
+        gp_section_header(gpc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
 
         av_freep(&sec_ctx.context_id);
 
@@ -631,7 +818,7 @@ static int print_streams(GraphPrintContext *gpc, InputFile 
**ifiles, int nb_ifil
 
             sec_ctx.context_type = 
av_get_media_type_string(ist->par->codec_type);
 
-            avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
+            gp_section_header(gpc, &sec_ctx, SECTION_ID_INPUTSTREAM);
             av_freep(&sec_ctx.context_id);
             sec_ctx.context_type = NULL;
 
@@ -657,14 +844,14 @@ static int print_streams(GraphPrintContext *gpc, 
InputFile **ifiles, int nb_ifil
             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
+            gp_section_footer(gpc); // SECTION_ID_INPUTSTREAM
         }
 
-        avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
-        avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
+        gp_section_footer(gpc); // SECTION_ID_INPUTSTREAMS
+        gp_section_footer(gpc); // SECTION_ID_INPUTFILE
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
+    gp_section_footer(gpc); // SECTION_ID_INPUTFILES
 
 
     print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
@@ -682,7 +869,7 @@ static int print_streams(GraphPrintContext *gpc, InputFile 
**ifiles, int nb_ifil
             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);
+            gp_section_header(gpc, &sec_ctx, SECTION_ID_DECODER);
             av_freep(&sec_ctx.context_id);
             sec_ctx.context_type = NULL;
             sec_ctx.context_flags = 0;
@@ -697,11 +884,11 @@ static int print_streams(GraphPrintContext *gpc, 
InputFile **ifiles, int nb_ifil
 
             print_str_opt("media_type", 
av_get_media_type_string(ist->par->codec_type));
 
-            avtext_print_section_footer(tfc); // SECTION_ID_DECODER
+            gp_section_footer(gpc); // SECTION_ID_DECODER
         }
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
+    gp_section_footer(gpc); // SECTION_ID_DECODERS
 
 
     print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
@@ -722,7 +909,7 @@ static int print_streams(GraphPrintContext *gpc, InputFile 
**ifiles, int nb_ifil
             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);
+            gp_section_header(gpc, &sec_ctx, SECTION_ID_ENCODER);
             av_freep(&sec_ctx.context_id);
             sec_ctx.context_type = NULL;
             sec_ctx.context_flags = 0;
@@ -736,11 +923,11 @@ static int print_streams(GraphPrintContext *gpc, 
InputFile **ifiles, int nb_ifil
 
             print_str_opt("media_type", av_get_media_type_string(ost->type));
 
-            avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
+            gp_section_footer(gpc); // SECTION_ID_ENCODER
         }
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
+    gp_section_footer(gpc); // SECTION_ID_ENCODERS
 
 
     print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
@@ -754,7 +941,7 @@ static int print_streams(GraphPrintContext *gpc, InputFile 
**ifiles, int nb_ifil
 
         sec_ctx.context_id = av_asprintf("Output_%d", n);
 
-        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
+        gp_section_header(gpc, &sec_ctx, SECTION_ID_OUTPUTFILE);
 
         av_freep(&sec_ctx.context_id);
 
@@ -773,7 +960,7 @@ static int print_streams(GraphPrintContext *gpc, InputFile 
**ifiles, int nb_ifil
 
         sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
 
-        avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
+        gp_section_header(gpc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
 
         av_freep(&sec_ctx.context_id);
 
@@ -783,7 +970,7 @@ static int print_streams(GraphPrintContext *gpc, InputFile 
**ifiles, int nb_ifil
 
             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);
+            gp_section_header(gpc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
             av_freep(&sec_ctx.context_id);
             sec_ctx.context_type = NULL;
 
@@ -802,17 +989,17 @@ static int print_streams(GraphPrintContext *gpc, 
InputFile **ifiles, int nb_ifil
 
             print_str_opt("media_type", av_get_media_type_string(ost->type));
 
-            avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
+            gp_section_footer(gpc); // SECTION_ID_OUTPUTSTREAM
         }
 
-        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
-        avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
+        gp_section_footer(gpc); // SECTION_ID_OUTPUTSTREAMS
+        gp_section_footer(gpc); // SECTION_ID_OUTPUTFILE
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
+    gp_section_footer(gpc); // SECTION_ID_OUTPUTFILES
 
 
-    avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
+    gp_section_header(gpc, NULL, SECTION_ID_STREAMLINKS);
 
     for (int n = 0; n < nb_ofiles; n++) {
         OutputFile *of = ofiles[n];
@@ -822,7 +1009,7 @@ static int print_streams(GraphPrintContext *gpc, InputFile 
**ifiles, int nb_ifil
 
             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);
+                gp_section_header(gpc, &sec_ctx, SECTION_ID_STREAMLINK);
                 sec_ctx.context_type = NULL;
 
                 if (ost->enc) {
@@ -837,12 +1024,12 @@ static int print_streams(GraphPrintContext *gpc, 
InputFile **ifiles, int nb_ifil
 
                 print_str_opt("media_type", 
av_get_media_type_string(ost->type));
 
-                avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
+                gp_section_footer(gpc); // SECTION_ID_STREAMLINK
             }
         }
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
+    gp_section_footer(gpc); // SECTION_ID_STREAMLINKS
 
     av_bprint_finalize(&buf, NULL);
     return 0;
@@ -863,7 +1050,7 @@ static void uninit_graphprint(GraphPrintContext *gpc)
     av_freep(&gpc);
 }
 
-static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
+static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf, int 
record)
 {
     const AVTextFormatter *text_formatter;
     AVTextFormatContext *tfc = NULL;
@@ -884,20 +1071,22 @@ static int init_graphprint(GraphPrintContext **pgpc, 
AVBPrint *target_buf)
         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;
-    }
+    if (!record) {
+        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 };
-    const char *w_args = print_graphs_format ? strchr(print_graphs_format, 
'=') : NULL;
-    if (w_args)
-        ++w_args; // consume '='
-    ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, 
FF_ARRAY_ELEMS(sections), tf_options, NULL);
-    if (ret < 0) {
-        goto fail;
+        AVTextFormatOptions tf_options = { .show_optional_fields = -1 };
+        const char *w_args = print_graphs_format ? strchr(print_graphs_format, 
'=') : NULL;
+        if (w_args)
+            ++w_args; // consume '='
+        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));
@@ -908,20 +1097,24 @@ static int init_graphprint(GraphPrintContext **pgpc, 
AVBPrint *target_buf)
 
     gpc->wctx = wctx;
     gpc->tfc = tfc;
+    gpc->rec = record ? target_buf : NULL;
     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);
+    gpc->is_diagram = !!(text_formatter->flags & 
AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
     if (gpc->is_diagram) {
-        tfc->opts.show_value_unit = 1;
-        tfc->opts.show_optional_fields = -1;
+        gpc->show_value_unit = 1;
         gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
         gpc->skip_buffer_filters = 1;
+        if (tfc) {
+            tfc->opts.show_value_unit = 1;
+            tfc->opts.show_optional_fields = -1;
+        }
         ////} else {
         ////    gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
     }
 
-    if (!strcmp(text_formatter->name, "mermaid") || 
!strcmp(text_formatter->name, "mermaidhtml")) {
+    if (tfc && (!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"))
@@ -950,68 +1143,53 @@ int print_filtergraph(FilterGraph *fg, AVFilterGraph 
*graph)
     av_assert2(fg);
 
     GraphPrintContext *gpc = NULL;
-    AVTextFormatContext *tfc;
     AVBPrint *target_buf = &fg->graph_print_buf;
     int ret;
 
     if (target_buf->len)
         av_bprint_finalize(target_buf, NULL);
 
-    ret = init_graphprint(&gpc, target_buf);
+    // Due to the threading model each graph needs to print itself from its own
+    // thread. The actual printing happens short before cleanup in ffmpeg.c
+    // where all graphs are assembled together, so the print calls are recorded
+    // here and replayed there.
+    ret = init_graphprint(&gpc, target_buf, 1);
     if (ret)
         return ret;
 
-    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);
-
+    gp_section_header(gpc, NULL, SECTION_ID_FILTERGRAPH);
     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
-    }
+    gp_section_footer(gpc); // SECTION_ID_FILTERGRAPH
 
     uninit_graphprint(gpc);
 
     return 0;
 }
 
+static void print_recorded_graph(const GraphPrintContext *gpc, AVBPrint 
*graph_buf)
+{
+    if (graph_buf->len > 0) {
+        if (gp_replay(gpc, graph_buf) < 0)
+            av_log(NULL, AV_LOG_ERROR, "Invalid record of a filter graph\n");
+        av_bprint_finalize(graph_buf, NULL);
+    }
+}
+
 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);
+    ret = init_graphprint(&gpc, &target_buf, 0);
     if (ret)
         goto cleanup;
 
-    tfc = gpc->tfc;
+    gp_section_header(gpc, NULL, SECTION_ID_ROOT);
+    gp_section_header(gpc, NULL, SECTION_ID_FILTERGRAPHS);
 
-    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++) {
-        AVBPrint *graph_buf = &graphs[i]->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 i = 0; i < nb_graphs; i++)
+        print_recorded_graph(gpc, &graphs[i]->graph_print_buf);
 
     for (int n = 0; n < nb_ofiles; n++) {
         OutputFile *of = ofiles[n];
@@ -1019,24 +1197,16 @@ static int print_filtergraphs_priv(FilterGraph 
**graphs, int nb_graphs, InputFil
         for (int i = 0; i < of->nb_streams; i++) {
             OutputStream *ost = of->streams[i];
 
-            if (ost->fg_simple) {
-                AVBPrint *graph_buf = &ost->fg_simple->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
-                }
-            }
+            if (ost->fg_simple)
+                print_recorded_graph(gpc, &ost->fg_simple->graph_print_buf);
         }
     }
 
-    avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
+    gp_section_footer(gpc); // SECTION_ID_FILTERGRAPHS
 
     print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
 
-    avtext_print_section_footer(tfc); // SECTION_ID_ROOT
+    gp_section_footer(gpc); // SECTION_ID_ROOT
 
     if (print_graphs_file) {
         AVIOContext *avio = NULL;
diff --git a/tests/ref/fate/ffmpeg-print-graphs-compact 
b/tests/ref/fate/ffmpeg-print-graphs-compact
index a5b136efeb..de852ddc65 100644
--- a/tests/ref/fate/ffmpeg-print-graphs-compact
+++ b/tests/ref/fate/ffmpeg-print-graphs-compact
@@ -11,7 +11,6 @@ filter|filter_name=buffer|description=Buffer video frames, 
and make them accessi
 
filter_output|dest_filter_id=G0_Parsed_scale_0|output_index=0|pad_name=default|dest_pad_name=default|filter_id=G0_graph_0_input_from_stream_0_0|media_type=video|format=yuv420p|width=64|height=64|sar=1:1|sample_rate=1/25
 filter|filter_name=buffersink|description=Buffer video frames, and make them 
available to the end of the filter graph.|nb_inputs=1|nb_outputs=0
 
filter_input|input_index=0|pad_name=default|source_filter_id=G0_Parsed_format_1|source_pad_name=default|filter_id=G0_out__0_0|media_type=video|format=yuv420p|width=32|height=32|sar=1:1|sample_rate=1/25
-
 
inputfile|index=0|demuxer_name=lavfi|file_extension=08|url=testsrc2=s=64x64:r=25:d=0.08
 inputstream|id=r_in_0_0|name=AVFrame to AVPacket 
passthrough|index=0|media_type=video
 decoder|source_id=r_in_0_0|id=in_0_0|name=wrapped_avframe|media_type=video
-- 
2.52.0


From 7b59ed5fb15b80ad25990e50e7d243d0f8ea9ee2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 8 Oct 2026 03:38:25 +0200
Subject: [PATCH 3/5] fftools/textformat: assemble each compact row in a buffer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Collect the fields of a row and the lines of the rows nested in it,
write them when the row ends, and write the separator before each field.
This fixes rows fused with the previous one after a typed element had
printed at the same level, the blank line after the timecode rows of a
frame, fields printed on a separator-led line after nested rows, and the
doubled or trailing separators of empty rows and empty inline arrays,
which the tmcd refs encoded.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 fftools/textformat/tf_compact.c               | 143 +++++++++++-------
 .../fate/ffprobe-group-stream-entries-tmcd    |  12 +-
 tests/ref/fate/ffprobe-show-groups-tmcd       |  14 +-
 tests/ref/fate/ffprobe-stream-entries-tmcd    |   2 +-
 tests/ref/fate/h264-timecode-probe            |   2 -
 tests/ref/fate/mpegts-probe-program-compact   |   3 +-
 6 files changed, 104 insertions(+), 72 deletions(-)

diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c
index 4b4db7bca2..df59dea0ef 100644
--- a/fftools/textformat/tf_compact.c
+++ b/fftools/textformat/tf_compact.c
@@ -91,9 +91,14 @@ typedef struct CompactContext {
     int print_section;
     char *escape_mode_str;
     const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, 
void *log_ctx);
-    int nested_section[SECTION_MAX_NB_LEVELS];
-    int has_nested_elems[SECTION_MAX_NB_LEVELS];
-    int terminate_line[SECTION_MAX_NB_LEVELS];
+    /** level of the row whose line the fields at this level belong to, -1 for 
none */
+    int row_level[SECTION_MAX_NB_LEVELS];
+    /** number of fields written to the row at this level */
+    int nb_fields[SECTION_MAX_NB_LEVELS];
+    /** the line of the row at this level */
+    AVBPrint line[SECTION_MAX_NB_LEVELS];
+    /** the lines of the rows nested in the row at this level */
+    AVBPrint rows[SECTION_MAX_NB_LEVELS];
 } CompactContext;
 
 #undef OFFSET
@@ -117,6 +122,11 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
 {
     CompactContext *compact = wctx->priv;
 
+    for (int i = 0; i < SECTION_MAX_NB_LEVELS; i++) {
+        av_bprint_init(&compact->line[i], 1, AV_BPRINT_SIZE_UNLIMITED);
+        av_bprint_init(&compact->rows[i], 1, AV_BPRINT_SIZE_UNLIMITED);
+    }
+
     if (strlen(compact->item_sep_str) != 1) {
         av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must 
contain a single character\n",
                compact->item_sep_str);
@@ -138,43 +148,49 @@ static av_cold int compact_init(AVTextFormatContext *wctx)
     return 0;
 }
 
-static int array_has_inline_elems(const AVTextFormatContext *wctx,
-                                  const AVTextFormatSection *section)
+static av_cold int compact_uninit(AVTextFormatContext *wctx)
 {
-    for (int i = 0; section->children_ids[i] != -1; i++)
-        if (wctx->sections[section->children_ids[i]].flags & 
AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE)
-            return 1;
+    CompactContext *compact = wctx->priv;
+
+    for (int i = 0; i < SECTION_MAX_NB_LEVELS; i++) {
+        av_bprint_finalize(&compact->line[i], NULL);
+        av_bprint_finalize(&compact->rows[i], NULL);
+    }
+
     return 0;
 }
 
+static void bprint_append_bprint(AVBPrint *dst, const AVBPrint *src)
+{
+    av_bprint_append_data(dst, src->str, FFMIN(src->len, src->size - 1));
+}
+
 static void compact_print_section_header(AVTextFormatContext *wctx, const void 
*data)
 {
     CompactContext *compact = wctx->priv;
     const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
     const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, 
wctx->level);
+    int level = wctx->level;
 
     if (!section)
         return;
 
-    compact->terminate_line[wctx->level] = 1;
-    compact->has_nested_elems[wctx->level] = 0;
+    av_bprint_clear(&wctx->section_pbuf[level]);
+    compact->row_level[level] = level ? compact->row_level[level - 1] : -1;
 
-    av_bprint_clear(&wctx->section_pbuf[wctx->level]);
     if (parent_section &&
         (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ||
             (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) &&
                 !(parent_section->flags & 
(AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | 
AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) {
 
         /* define a prefix for elements not contained in an array or
-           in a wrapper, or for array elements with a type */
+           in a wrapper, or for array elements with a type, their
+           fields continue the line of the enclosing row */
         const char *element_name = (char *)av_x_if_null(section->element_name, 
section->name);
-        AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
-
-        compact->nested_section[wctx->level] = 1;
-        compact->has_nested_elems[wctx->level - 1] = 1;
+        AVBPrint *section_pbuf = &wctx->section_pbuf[level];
 
         av_bprintf(section_pbuf, "%s%s",
-                   wctx->section_pbuf[wctx->level - 1].str, element_name);
+                   wctx->section_pbuf[level - 1].str, element_name);
 
         if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
             // add /TYPE to prefix
@@ -190,39 +206,65 @@ static void 
compact_print_section_header(AVTextFormatContext *wctx, const void *
             }
         }
         av_bprint_chars(section_pbuf, ':', 1);
-
-        wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level - 1];
-    } else {
-        if (parent_section && !(parent_section->flags & 
(AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) 
{
-            int *parent_open = &compact->terminate_line[wctx->level - 1];
-            if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY && 
!array_has_inline_elems(wctx, section)) {
-                /* Row elements end the parent's line, its footer must not do 
it again. */
-                if (*parent_open)
-                    writer_w8(wctx, '\n');
-                *parent_open = 0;
-            } else if (wctx->level && wctx->nb_item[wctx->level - 1]) {
-                writer_w8(wctx, compact->item_sep);
-                *parent_open = 1;
-            }
-        }
-        if (compact->print_section &&
-            !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | 
AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
-            writer_printf(wctx, "%s%c", section->name, compact->item_sep);
+        return;
     }
+
+    if (section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | 
AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))
+        return;
+
+    /* a row, printed as one line once the section ends */
+    compact->row_level[level] = level;
+    compact->nb_fields[level] = 0;
+    av_bprint_clear(&compact->line[level]);
+    av_bprint_clear(&compact->rows[level]);
+    if (compact->print_section)
+        av_bprintf(&compact->line[level], "%s", section->name);
 }
 
 static void compact_print_section_footer(AVTextFormatContext *wctx)
 {
     CompactContext *compact = wctx->priv;
     const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
+    int level = wctx->level;
+    int parent_row;
 
-    if (!section)
+    if (!section || compact->row_level[level] != level)
         return;
 
-    if (!compact->nested_section[wctx->level] &&
-        compact->terminate_line[wctx->level] &&
-        !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | 
AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
-        writer_w8(wctx, '\n');
+    /* the row's line, followed by the lines of the rows nested in it */
+    av_bprint_chars(&compact->line[level], '\n', 1);
+    bprint_append_bprint(&compact->line[level], &compact->rows[level]);
+
+    parent_row = level ? compact->row_level[level - 1] : -1;
+    if (parent_row >= 0)
+        bprint_append_bprint(&compact->rows[parent_row], 
&compact->line[level]);
+    else
+        writer_put_str(wctx, compact->line[level].str);
+}
+
+static void compact_print_field(AVTextFormatContext *wctx, const char *key, 
const char *value)
+{
+    CompactContext *compact = wctx->priv;
+    int row = compact->row_level[wctx->level];
+    AVBPrint tmp, *line = &tmp;
+
+    if (row >= 0) {
+        line = &compact->line[row];
+        if (compact->nb_fields[row]++ || compact->print_section)
+            av_bprint_chars(line, compact->item_sep, 1);
+    } else {
+        /* a field outside of any row */
+        av_bprint_init(&tmp, 1, AV_BPRINT_SIZE_UNLIMITED);
+    }
+
+    if (!compact->nokey)
+        av_bprintf(line, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
+    av_bprintf(line, "%s", value);
+
+    if (line == &tmp) {
+        writer_put_str(wctx, tmp.str);
+        av_bprint_finalize(&tmp, NULL);
+    }
 }
 
 static void compact_print_str(AVTextFormatContext *wctx, const char *key, 
const char *value)
@@ -230,34 +272,24 @@ 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 (!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));
+    compact_print_field(wctx, key, compact->escape_str(&buf, value, 
compact->item_sep, wctx));
     av_bprint_finalize(&buf, NULL);
 }
 
 static void compact_print_int(AVTextFormatContext *wctx, const char *key, 
int64_t value)
 {
-    CompactContext *compact = wctx->priv;
+    char buf[32];
 
-    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);
+    snprintf(buf, sizeof(buf), "%"PRId64, value);
+    compact_print_field(wctx, key, buf);
 }
 
 const AVTextFormatter avtextformatter_compact = {
     .name                 = "compact",
     .priv_size            = sizeof(CompactContext),
     .init                 = compact_init,
+    .uninit               = compact_uninit,
     .print_section_header = compact_print_section_header,
     .print_section_footer = compact_print_section_footer,
     .print_integer        = compact_print_int,
@@ -289,6 +321,7 @@ const AVTextFormatter avtextformatter_csv = {
     .name                 = "csv",
     .priv_size            = sizeof(CompactContext),
     .init                 = compact_init,
+    .uninit               = compact_uninit,
     .print_section_header = compact_print_section_header,
     .print_section_footer = compact_print_section_footer,
     .print_integer        = compact_print_int,
diff --git a/tests/ref/fate/ffprobe-group-stream-entries-tmcd 
b/tests/ref/fate/ffprobe-group-stream-entries-tmcd
index 6ba54f4200..60630b17ed 100644
--- a/tests/ref/fate/ffprobe-group-stream-entries-tmcd
+++ b/tests/ref/fate/ffprobe-group-stream-entries-tmcd
@@ -1,6 +1,6 @@
-stream_group|
-stream|index=0|
-stream_group|
-stream|index=0|
-stream_group|
-stream|index=0|
+stream_group
+stream|index=0
+stream_group
+stream|index=0
+stream_group
+stream|index=0
diff --git a/tests/ref/fate/ffprobe-show-groups-tmcd 
b/tests/ref/fate/ffprobe-show-groups-tmcd
index 64b32fe905..f2766220f7 100644
--- a/tests/ref/fate/ffprobe-show-groups-tmcd
+++ b/tests/ref/fate/ffprobe-show-groups-tmcd
@@ -1,7 +1,7 @@
-stream_group|
-stream|index=0|
-stream_group|
-stream|index=0|
-stream_group|
-stream|index=0|
-stream|index=0|
+stream_group
+stream|index=0
+stream_group
+stream|index=0
+stream_group
+stream|index=0
+stream|index=0
diff --git a/tests/ref/fate/ffprobe-stream-entries-tmcd 
b/tests/ref/fate/ffprobe-stream-entries-tmcd
index 64b0ce91b9..c3700a518c 100644
--- a/tests/ref/fate/ffprobe-stream-entries-tmcd
+++ b/tests/ref/fate/ffprobe-stream-entries-tmcd
@@ -1 +1 @@
-stream|index=0|
+stream|index=0
diff --git a/tests/ref/fate/h264-timecode-probe 
b/tests/ref/fate/h264-timecode-probe
index dffe7ae762..c30a8e5a3a 100644
--- a/tests/ref/fate/h264-timecode-probe
+++ b/tests/ref/fate/h264-timecode-probe
@@ -1,8 +1,6 @@
 
frame|key_frame=1|pict_type=I|side_datum/h_26_45__user_data_unregistered_sei_message:side_data_type=H.26[45]
 User Data Unregistered SEI 
message|side_datum/smpte_12_1_timecode:side_data_type=SMPTE 12-1 timecode
 timecode|value=00:00:00:00
 timecode|value=00:00:00:00
-
 
frame|key_frame=0|pict_type=P|side_datum/smpte_12_1_timecode:side_data_type=SMPTE
 12-1 timecode
 timecode|value=00:00:00:02
 timecode|value=00:00:00:02
-
diff --git a/tests/ref/fate/mpegts-probe-program-compact 
b/tests/ref/fate/mpegts-probe-program-compact
index bd884a0289..db18b05dde 100644
--- a/tests/ref/fate/mpegts-probe-program-compact
+++ b/tests/ref/fate/mpegts-probe-program-compact
@@ -1,4 +1,5 @@
 
packet|stream_index=0|pts=2863340144|side_datum/mpegts_stream_id:side_data_type=MPEGTS
 Stream ID|side_datum/mpegts_stream_id:id=224
 
packet|stream_index=0|pts=2863338344|side_datum/mpegts_stream_id:side_data_type=MPEGTS
 Stream ID|side_datum/mpegts_stream_id:id=224
 program|program_id=769|nb_streams=2
-stream|index=0|codec_name=hevcstream|index=1|codec_name=aac_latm
\ No newline at end of file
+stream|index=0|codec_name=hevc
+stream|index=1|codec_name=aac_latm
-- 
2.52.0


From 1d00d44f452d00b4e86f860a501573d3044e90ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 8 Oct 2026 03:38:25 +0200
Subject: [PATCH 4/5] fftools/textformat: derive the default writer's nesting
 from the section flags
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The per-level flag was set for nested sections and never cleared.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 fftools/textformat/tf_default.c | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c
index 78a463dd49..22e4d44412 100644
--- a/fftools/textformat/tf_default.c
+++ b/fftools/textformat/tf_default.c
@@ -36,7 +36,6 @@ typedef struct DefaultContext {
     const AVClass *class;
     int nokey;
     int noprint_wrappers;
-    int nested_section[SECTION_MAX_NB_LEVELS];
 } DefaultContext;
 
 #undef OFFSET
@@ -63,27 +62,32 @@ static inline char *upcase_string(char *dst, size_t 
dst_size, const char *src)
     return dst;
 }
 
+static int default_is_nested(AVTextFormatContext *wctx)
+{
+    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, 
wctx->level);
+
+    return parent_section &&
+           !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | 
AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY));
+}
+
 static void default_print_section_header(AVTextFormatContext *wctx, const void 
*data)
 {
     DefaultContext *def = wctx->priv;
     char buf[32];
     const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
-    const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, 
wctx->level);
+    int nested = default_is_nested(wctx);
 
     if (!section)
         return;
 
     av_bprint_clear(&wctx->section_pbuf[wctx->level]);
-    if (parent_section &&
-        !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | 
AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) {
-        def->nested_section[wctx->level] = 1;
+    if (nested)
         av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:",
                    wctx->section_pbuf[wctx->level - 1].str,
                    upcase_string(buf, sizeof(buf),
                                  av_x_if_null(section->element_name, 
section->name)));
-    }
 
-    if (def->noprint_wrappers || def->nested_section[wctx->level])
+    if (def->noprint_wrappers || nested)
         return;
 
     if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | 
AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
@@ -100,7 +104,7 @@ static void 
default_print_section_footer(AVTextFormatContext *wctx)
     if (!section)
         return;
 
-    if (def->noprint_wrappers || def->nested_section[wctx->level])
+    if (def->noprint_wrappers || default_is_nested(wctx))
         return;
 
     if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | 
AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
-- 
2.52.0


From d53e3f19e65a14a4677f1dba062f832e1e198801 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Thu, 8 Oct 2026 03:38:25 +0200
Subject: [PATCH 5/5] fftools/textformat: remove the unused DefaultContext from
 the ini writer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Kacper Michajłow <[email protected]>
---
 fftools/textformat/tf_ini.c | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c
index 63fccc940f..348054cc5b 100644
--- a/fftools/textformat/tf_ini.c
+++ b/fftools/textformat/tf_ini.c
@@ -29,15 +29,6 @@
 #include "libavutil/opt.h"
 #include "tf_internal.h"
 
-/* Default output */
-
-typedef struct DefaultContext {
-    const AVClass *class;
-    int nokey;
-    int noprint_wrappers;
-    int nested_section[SECTION_MAX_NB_LEVELS];
-} DefaultContext;
-
 /* INI format output */
 
 typedef struct INIContext {
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to