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]
