FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/graph/graphprint.c
Date: 2026-05-16 01:27:51
Exec Total Coverage
Lines: 0 523 0.0%
Functions: 0 17 0.0%
Branches: 0 286 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2018-2025 - softworkz
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /**
22 * @file
23 * output writers for filtergraph details
24 */
25
26 #include <string.h>
27 #include <stdatomic.h>
28
29 #include "graphprint.h"
30
31 #include "fftools/ffmpeg.h"
32 #include "fftools/ffmpeg_mux.h"
33
34 #include "libavutil/avassert.h"
35 #include "libavutil/avstring.h"
36 #include "libavutil/mem.h"
37 #include "libavutil/pixdesc.h"
38 #include "libavutil/dict.h"
39 #include "libavutil/common.h"
40 #include "libavfilter/avfilter.h"
41 #include "libavutil/buffer.h"
42 #include "libavutil/hwcontext.h"
43 #include "fftools/textformat/avtextformat.h"
44 #include "fftools/textformat/tf_mermaid.h"
45 #include "fftools/resources/resman.h"
46
47 typedef enum {
48 SECTION_ID_ROOT,
49 SECTION_ID_FILTERGRAPHS,
50 SECTION_ID_FILTERGRAPH,
51 SECTION_ID_GRAPH_INPUTS,
52 SECTION_ID_GRAPH_INPUT,
53 SECTION_ID_GRAPH_OUTPUTS,
54 SECTION_ID_GRAPH_OUTPUT,
55 SECTION_ID_FILTERS,
56 SECTION_ID_FILTER,
57 SECTION_ID_FILTER_INPUTS,
58 SECTION_ID_FILTER_INPUT,
59 SECTION_ID_FILTER_OUTPUTS,
60 SECTION_ID_FILTER_OUTPUT,
61 SECTION_ID_HWFRAMESCONTEXT,
62 SECTION_ID_INPUTFILES,
63 SECTION_ID_INPUTFILE,
64 SECTION_ID_INPUTSTREAMS,
65 SECTION_ID_INPUTSTREAM,
66 SECTION_ID_OUTPUTFILES,
67 SECTION_ID_OUTPUTFILE,
68 SECTION_ID_OUTPUTSTREAMS,
69 SECTION_ID_OUTPUTSTREAM,
70 SECTION_ID_STREAMLINKS,
71 SECTION_ID_STREAMLINK,
72 SECTION_ID_DECODERS,
73 SECTION_ID_DECODER,
74 SECTION_ID_ENCODERS,
75 SECTION_ID_ENCODER,
76 } SectionID;
77
78 static const AVTextFormatSection sections[] = {
79 [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "root", AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER, { SECTION_ID_FILTERGRAPHS, SECTION_ID_INPUTFILES, SECTION_ID_OUTPUTFILES, SECTION_ID_DECODERS, SECTION_ID_ENCODERS, SECTION_ID_STREAMLINKS, -1 } },
80
81 [SECTION_ID_FILTERGRAPHS] = { SECTION_ID_FILTERGRAPHS, "graphs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
82 [SECTION_ID_FILTERGRAPH] = { SECTION_ID_FILTERGRAPH, "graph", AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS, { SECTION_ID_GRAPH_INPUTS, SECTION_ID_GRAPH_OUTPUTS, SECTION_ID_FILTERS, -1 }, .element_name = "graph_info" },
83
84 [SECTION_ID_GRAPH_INPUTS] = { SECTION_ID_GRAPH_INPUTS, "graph_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_INPUT, -1 }, .id_key = "id" },
85 [SECTION_ID_GRAPH_INPUT] = { SECTION_ID_GRAPH_INPUT, "graph_input", 0, { -1 }, .id_key = "filter_id" },
86
87 [SECTION_ID_GRAPH_OUTPUTS] = { SECTION_ID_GRAPH_OUTPUTS, "graph_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_OUTPUT, -1 }, .id_key = "id" },
88 [SECTION_ID_GRAPH_OUTPUT] = { SECTION_ID_GRAPH_OUTPUT, "graph_output", 0, { -1 }, .id_key = "filter_id" },
89
90 [SECTION_ID_FILTERS] = { SECTION_ID_FILTERS, "filters", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_FILTER, -1 }, .id_key = "graph_id" },
91 [SECTION_ID_FILTER] = { SECTION_ID_FILTER, "filter", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { SECTION_ID_FILTER_INPUTS, SECTION_ID_FILTER_OUTPUTS, -1 }, .id_key = "filter_id" },
92
93 [SECTION_ID_FILTER_INPUTS] = { SECTION_ID_FILTER_INPUTS, "filter_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_INPUT, -1 } },
94 [SECTION_ID_FILTER_INPUT] = { SECTION_ID_FILTER_INPUT, "filter_input", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "source_filter_id", .dest_id_key = "filter_id" },
95
96 [SECTION_ID_FILTER_OUTPUTS] = { SECTION_ID_FILTER_OUTPUTS, "filter_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_OUTPUT, -1 } },
97 [SECTION_ID_FILTER_OUTPUT] = { SECTION_ID_FILTER_OUTPUT, "filter_output", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { SECTION_ID_HWFRAMESCONTEXT, -1 }, .id_key = "filter_id", .src_id_key = "filter_id", .dest_id_key = "dest_filter_id" },
98
99 [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context", 0, { -1 }, },
100
101 [SECTION_ID_INPUTFILES] = { SECTION_ID_INPUTFILES, "inputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTFILE, -1 }, .id_key = "id" },
102 [SECTION_ID_INPUTFILE] = { SECTION_ID_INPUTFILE, "inputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAMS, -1 }, .id_key = "id" },
103
104 [SECTION_ID_INPUTSTREAMS] = { SECTION_ID_INPUTSTREAMS, "inputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAM, -1 }, .id_key = "id" },
105 [SECTION_ID_INPUTSTREAM] = { SECTION_ID_INPUTSTREAM, "inputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id" },
106
107 [SECTION_ID_OUTPUTFILES] = { SECTION_ID_OUTPUTFILES, "outputfiles", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTFILE, -1 }, .id_key = "id" },
108 [SECTION_ID_OUTPUTFILE] = { SECTION_ID_OUTPUTFILE, "outputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAMS, -1 }, .id_key = "id" },
109
110 [SECTION_ID_OUTPUTSTREAMS] = { SECTION_ID_OUTPUTSTREAMS, "outputstreams", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAM, -1 }, .id_key = "id" },
111 [SECTION_ID_OUTPUTSTREAM] = { SECTION_ID_OUTPUTSTREAM, "outputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id", },
112
113 [SECTION_ID_STREAMLINKS] = { SECTION_ID_STREAMLINKS, "streamlinks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAMLINK, -1 } },
114 [SECTION_ID_STREAMLINK] = { SECTION_ID_STREAMLINK, "streamlink", AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .src_id_key = "source_stream_id", .dest_id_key = "dest_stream_id" },
115
116 [SECTION_ID_DECODERS] = { SECTION_ID_DECODERS, "decoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_DECODER, -1 } },
117 [SECTION_ID_DECODER] = { SECTION_ID_DECODER, "decoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "source_id", .dest_id_key = "id" },
118
119 [SECTION_ID_ENCODERS] = { SECTION_ID_ENCODERS, "encoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_ENCODER, -1 } },
120 [SECTION_ID_ENCODER] = { SECTION_ID_ENCODER, "encoder", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS | AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS, { -1 }, .id_key = "id", .src_id_key = "id", .dest_id_key = "dest_id" },
121 };
122
123 typedef struct GraphPrintContext {
124 AVTextFormatContext *tfc;
125 AVTextWriterContext *wctx;
126 AVDiagramConfig diagram_config;
127
128 int id_prefix_num;
129 int is_diagram;
130 int opt_flags;
131 int skip_buffer_filters;
132 AVBPrint pbuf;
133
134 } GraphPrintContext;
135
136 /* Text Format API Shortcuts */
137 #define print_id(k, v) print_sanizied_id(gpc, k, v, 0)
138 #define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
139 #define print_int(k, v) avtext_print_integer(tfc, k, v, 0)
140 #define print_int_opt(k, v) avtext_print_integer(tfc, k, v, gpc->opt_flags)
141 #define print_q(k, v, s) avtext_print_rational(tfc, k, v, s)
142 #define print_str(k, v) avtext_print_string(tfc, k, v, 0)
143 #define print_str_opt(k, v) avtext_print_string(tfc, k, v, gpc->opt_flags)
144 #define print_val(k, v, u) avtext_print_unit_integer(tfc, k, v, u)
145
146 #define print_fmt(k, f, ...) do { \
147 av_bprint_clear(&gpc->pbuf); \
148 av_bprintf(&gpc->pbuf, f, __VA_ARGS__); \
149 avtext_print_string(tfc, k, gpc->pbuf.str, 0); \
150 } while (0)
151
152 #define print_fmt_opt(k, f, ...) do { \
153 av_bprint_clear(&gpc->pbuf); \
154 av_bprintf(&gpc->pbuf, f, __VA_ARGS__); \
155 avtext_print_string(tfc, k, gpc->pbuf.str, gpc->opt_flags); \
156 } while (0)
157
158
159 static atomic_int prefix_num = 0;
160
161 static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
162 {
163 unsigned i;
164 for (i = 0; src[i] && i < dst_size - 1; i++)
165 dst[i] = (char)av_toupper(src[i]);
166 dst[i] = 0;
167 return dst;
168 }
169
170 static char *get_extension(const char *url)
171 {
172 const char *dot = NULL;
173 const char *sep = NULL;
174 const char *end;
175
176 if (!url)
177 return NULL;
178
179 /* Stop at the first query ('?') or fragment ('#') delimiter so they
180 * are not considered part of the path. */
181 end = strpbrk(url, "?#");
182 if (!end)
183 end = url + strlen(url);
184
185 /* Scan the path component only. */
186 for (const char *p = url; p < end; p++) {
187 if (*p == '.')
188 dot = p;
189 else if (*p == '/' || *p == '\\')
190 sep = p;
191 }
192
193 /* Validate that we have a proper extension. */
194 if (dot && dot != url && (!sep || dot > sep + 1) && (dot + 1) < end) {
195 /* Use FFmpeg helper to duplicate the substring. */
196 return av_strndup(dot + 1, end - (dot + 1));
197 }
198
199 return NULL;
200 }
201
202 static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
203 {
204 AVTextFormatContext *tfc = gpc->tfc;
205
206 if (!hw_device_context)
207 return;
208
209 print_int_opt("has_hw_device_context", 1);
210 print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
211 }
212
213 static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
214 {
215 AVTextFormatContext *tfc = gpc->tfc;
216 const AVPixFmtDescriptor *pix_desc_hw;
217 const AVPixFmtDescriptor *pix_desc_sw;
218
219 if (!hw_frames_context || !hw_frames_context->device_ctx)
220 return;
221
222 avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT);
223
224 print_int_opt("has_hw_frames_context", 1);
225 print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
226
227 pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
228 if (pix_desc_hw) {
229 print_str("hw_pixel_format", pix_desc_hw->name);
230 if (pix_desc_hw->alias)
231 print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
232 }
233
234 pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
235 if (pix_desc_sw) {
236 print_str("sw_pixel_format", pix_desc_sw->name);
237 if (pix_desc_sw->alias)
238 print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
239 }
240
241 print_int_opt("width", hw_frames_context->width);
242 print_int_opt("height", hw_frames_context->height);
243 print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
244
245 avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT
246 }
247
248 static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
249 {
250 AVTextFormatContext *tfc = gpc->tfc;
251 AVBufferRef *hw_frames_ctx;
252 char layout_string[64];
253
254 if (!link)
255 return;
256
257 hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
258
259 print_str_opt("media_type", av_get_media_type_string(link->type));
260
261 switch (link->type) {
262 case AVMEDIA_TYPE_VIDEO:
263
264 if (hw_frames_ctx && hw_frames_ctx->data) {
265 AVHWFramesContext * hwfctx = (AVHWFramesContext *)hw_frames_ctx->data;
266 const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
267 const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
268 if (pix_desc_hw && pix_desc_sw)
269 print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
270 } else {
271 print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
272 }
273
274 if (link->w && link->h) {
275 if (tfc->opts.show_value_unit) {
276 print_fmt("size", "%dx%d", link->w, link->h);
277 } else {
278 print_int("width", link->w);
279 print_int("height", link->h);
280 }
281 }
282
283 print_q("sar", link->sample_aspect_ratio, ':');
284
285 if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
286 print_str_opt("color_range", av_color_range_name(link->color_range));
287
288 if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
289 print_str("color_space", av_color_space_name(link->colorspace));
290 break;
291
292 case AVMEDIA_TYPE_SUBTITLE:
293 ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
294
295 if (link->w && link->h) {
296 if (tfc->opts.show_value_unit) {
297 print_fmt("size", "%dx%d", link->w, link->h);
298 } else {
299 print_int("width", link->w);
300 print_int("height", link->h);
301 }
302 }
303
304 break;
305
306 case AVMEDIA_TYPE_AUDIO:
307 av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
308 print_str("channel_layout", layout_string);
309 print_val("channels", link->ch_layout.nb_channels, "ch");
310 if (tfc->opts.show_value_unit)
311 print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
312 else
313 print_val("sample_rate", link->sample_rate, "Hz");
314
315 break;
316 }
317
318 print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
319
320 if (hw_frames_ctx && hw_frames_ctx->data)
321 print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
322 av_buffer_unref(&hw_frames_ctx);
323 }
324
325 static char sanitize_char(const char c)
326 {
327 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
328 return c;
329 return '_';
330 }
331
332 static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
333 {
334 AVTextFormatContext *tfc = gpc->tfc;
335 AVBPrint buf;
336
337 if (!key || !id_str)
338 return;
339
340 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
341
342 if (!skip_prefix)
343 av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
344
345 // sanizize section id
346 for (const char *p = id_str; *p; p++)
347 av_bprint_chars(&buf, sanitize_char(*p), 1);
348
349 print_str(key, buf.str);
350
351 av_bprint_finalize(&buf, NULL);
352 }
353
354 static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
355 {
356 AVTextFormatContext *tfc = gpc->tfc;
357 AVTextFormatSectionContext sec_ctx = { 0 };
358 AVBPrint buf;
359
360 if (!id_str)
361 return;
362
363 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
364
365 if (!skip_prefix)
366 av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
367
368 // sanizize section id
369 for (const char *p = id_str; *p; p++)
370 av_bprint_chars(&buf, sanitize_char(*p), 1);
371
372 sec_ctx.context_id = buf.str;
373
374 avtext_print_section_header(tfc, &sec_ctx, section_id);
375
376 av_bprint_finalize(&buf, NULL);
377 }
378
379 static const char *get_filterpad_name(const AVFilterPad *pad)
380 {
381 return pad ? avfilter_pad_get_name(pad, 0) : "pad";
382 }
383
384 static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
385 {
386 AVTextFormatContext *tfc = gpc->tfc;
387 AVTextFormatSectionContext sec_ctx = { 0 };
388
389 print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
390
391 ////print_id("filter_id", filter->name);
392
393 if (filter->filter) {
394 print_str("filter_name", filter->filter->name);
395 print_str_opt("description", filter->filter->description);
396 print_int_opt("nb_inputs", filter->nb_inputs);
397 print_int_opt("nb_outputs", filter->nb_outputs);
398 }
399
400 if (filter->hw_device_ctx) {
401 AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
402 print_hwdevicecontext(gpc, device_context);
403 if (filter->extra_hw_frames > 0)
404 print_int("extra_hw_frames", filter->extra_hw_frames);
405 }
406
407 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
408
409 for (unsigned i = 0; i < filter->nb_inputs; i++) {
410 AVDictionaryEntry *dic_entry;
411 AVFilterLink *link = filter->inputs[i];
412
413 sec_ctx.context_type = av_get_media_type_string(link->type);
414 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
415 sec_ctx.context_type = NULL;
416
417 print_int_opt("input_index", i);
418 print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
419
420 dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
421 if (dic_entry) {
422 char buf[256];
423 (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
424 print_id_noprefix("source_filter_id", buf);
425 } else {
426 print_id("source_filter_id", link->src->name);
427 }
428
429 print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
430 print_id("filter_id", filter->name);
431
432 print_link(gpc, link);
433
434 avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
435 }
436
437 avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
438
439 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
440
441 for (unsigned i = 0; i < filter->nb_outputs; i++) {
442 AVDictionaryEntry *dic_entry;
443 AVFilterLink *link = filter->outputs[i];
444 char buf[256];
445
446 sec_ctx.context_type = av_get_media_type_string(link->type);
447 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
448 sec_ctx.context_type = NULL;
449
450 dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
451 if (dic_entry) {
452 (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
453 print_id_noprefix("dest_filter_id", buf);
454 } else {
455 print_id("dest_filter_id", link->dst->name);
456 }
457
458 print_int_opt("output_index", i);
459 print_str_opt("pad_name", get_filterpad_name(link->srcpad));
460 ////print_id("dest_filter_id", link->dst->name);
461 print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
462 print_id("filter_id", filter->name);
463
464 print_link(gpc, link);
465
466 avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
467 }
468
469 avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
470
471 avtext_print_section_footer(tfc); // SECTION_ID_FILTER
472 }
473
474 static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
475 {
476 AVTextFormatContext *tfc = gpc->tfc;
477 AVDictionary *input_map = NULL;
478 AVDictionary *output_map = NULL;
479
480 print_int("graph_index", fg->index);
481 print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
482 print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
483 print_str("description", fg->graph_desc);
484
485 print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
486
487 for (int i = 0; i < fg->nb_inputs; i++) {
488 InputFilter *ifilter = fg->inputs[i];
489 enum AVMediaType media_type = ifilter->type;
490
491 avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
492
493 print_int("input_index", ifilter->index);
494
495 if (ifilter->linklabel)
496 print_str("link_label", (const char*)ifilter->linklabel);
497
498 if (ifilter->filter) {
499 print_id("filter_id", ifilter->filter->name);
500 print_str("filter_name", ifilter->filter->filter->name);
501 }
502
503 if (ifilter->linklabel && ifilter->filter)
504 av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
505 else if (ifilter->input_name && ifilter->filter)
506 av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->input_name, 0);
507
508 print_str("media_type", av_get_media_type_string(media_type));
509
510 avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
511 }
512
513 avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
514
515 print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
516
517 for (int i = 0; i < fg->nb_outputs; i++) {
518 OutputFilter *ofilter = fg->outputs[i];
519
520 avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
521
522 print_int("output_index", ofilter->index);
523
524 print_str("name", ofilter->output_name);
525
526 if (fg->outputs[i]->linklabel)
527 print_str("link_label", (const char*)fg->outputs[i]->linklabel);
528
529 if (ofilter->filter) {
530 print_id("filter_id", ofilter->filter->name);
531 print_str("filter_name", ofilter->filter->filter->name);
532 }
533
534 if (ofilter->output_name && ofilter->filter)
535 av_dict_set(&output_map, ofilter->filter->name, ofilter->output_name, 0);
536
537
538 print_str("media_type", av_get_media_type_string(ofilter->type));
539
540 avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
541 }
542
543 avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
544
545 if (graph) {
546 AVTextFormatSectionContext sec_ctx = { 0 };
547
548 sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
549
550 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTERS);
551
552 if (gpc->is_diagram) {
553 print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
554 print_str("description", fg->graph_desc);
555 print_str("id", sec_ctx.context_id);
556 }
557
558 av_freep(&sec_ctx.context_id);
559
560 for (unsigned i = 0; i < graph->nb_filters; i++) {
561 AVFilterContext *filter = graph->filters[i];
562
563 if (gpc->skip_buffer_filters) {
564 if (av_dict_get(input_map, filter->name, NULL, 0))
565 continue;
566 if (av_dict_get(output_map, filter->name, NULL, 0))
567 continue;
568 }
569
570 sec_ctx.context_id = filter->name;
571
572 print_filter(gpc, filter, input_map, output_map);
573 }
574
575 avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
576 }
577
578 // Clean up dictionaries
579 av_dict_free(&input_map);
580 av_dict_free(&output_map);
581 }
582
583 static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
584 {
585 AVTextFormatContext *tfc = gpc->tfc;
586 AVBPrint buf;
587 AVTextFormatSectionContext sec_ctx = { 0 };
588
589 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
590
591 print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
592
593 for (int n = nb_ifiles - 1; n >= 0; n--) {
594 InputFile *ifi = ifiles[n];
595 AVFormatContext *fc = ifi->ctx;
596
597 sec_ctx.context_id = av_asprintf("Input_%d", n);
598 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
599 av_freep(&sec_ctx.context_id);
600
601 print_fmt("index", "%d", ifi->index);
602
603 if (fc) {
604 print_str("demuxer_name", fc->iformat->name);
605 if (fc->url) {
606 char *extension = get_extension(fc->url);
607 if (extension) {
608 print_str("file_extension", extension);
609 av_freep(&extension);
610 }
611 print_str("url", fc->url);
612 }
613 }
614
615 sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
616
617 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
618
619 av_freep(&sec_ctx.context_id);
620
621 for (int i = 0; i < ifi->nb_streams; i++) {
622 InputStream *ist = ifi->streams[i];
623 const AVCodecDescriptor *codec_desc;
624
625 if (!ist || !ist->par)
626 continue;
627
628 codec_desc = avcodec_descriptor_get(ist->par->codec_id);
629
630 sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
631
632 sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
633
634 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
635 av_freep(&sec_ctx.context_id);
636 sec_ctx.context_type = NULL;
637
638 av_bprint_clear(&buf);
639
640 print_fmt("id", "r_in_%d_%d", n, i);
641
642 if (codec_desc && codec_desc->name) {
643 ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
644 av_bprintf(&buf, "%s", codec_desc->long_name);
645 } else if (ist->dec) {
646 char char_buf[256];
647 av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
648 } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
649 av_bprintf(&buf, "%s", "Attachment");
650 } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
651 av_bprintf(&buf, "%s", "Data");
652 }
653
654 print_fmt("name", "%s", buf.str);
655 print_fmt("index", "%d", ist->index);
656
657 if (ist->dec)
658 print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
659
660 avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAM
661 }
662
663 avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
664 avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
665 }
666
667 avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
668
669
670 print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
671
672 for (int n = 0; n < nb_ifiles; n++) {
673 InputFile *ifi = ifiles[n];
674
675 for (int i = 0; i < ifi->nb_streams; i++) {
676 InputStream *ist = ifi->streams[i];
677
678 if (!ist->decoder)
679 continue;
680
681 sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
682 sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
683 sec_ctx.context_flags = 2;
684
685 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_DECODER);
686 av_freep(&sec_ctx.context_id);
687 sec_ctx.context_type = NULL;
688 sec_ctx.context_flags = 0;
689
690 av_bprint_clear(&buf);
691
692 print_fmt("source_id", "r_in_%d_%d", n, i);
693 print_fmt("id", "in_%d_%d", n, i);
694
695 ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
696 print_fmt("name", "%s", ist->dec->name);
697
698 print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
699
700 avtext_print_section_footer(tfc); // SECTION_ID_DECODER
701 }
702 }
703
704 avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
705
706
707 print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
708
709 for (int n = 0; n < nb_ofiles; n++) {
710 OutputFile *of = ofiles[n];
711
712 for (int i = 0; i < of->nb_streams; i++) {
713 OutputStream *ost = of->streams[i];
714 ////const AVCodecDescriptor *codec_desc;
715
716 if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
717 continue;
718
719 ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
720
721 sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
722 sec_ctx.context_type = av_get_media_type_string(ost->type);
723 sec_ctx.context_flags = 2;
724
725 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_ENCODER);
726 av_freep(&sec_ctx.context_id);
727 sec_ctx.context_type = NULL;
728 sec_ctx.context_flags = 0;
729
730 av_bprint_clear(&buf);
731
732 print_fmt("id", "out__%d_%d", n, i);
733 print_fmt("dest_id", "r_out__%d_%d", n, i);
734
735 print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
736
737 print_str_opt("media_type", av_get_media_type_string(ost->type));
738
739 avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
740 }
741 }
742
743 avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
744
745
746 print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
747
748 for (int n = nb_ofiles - 1; n >= 0; n--) {
749 OutputFile *of = ofiles[n];
750 Muxer *muxer = (Muxer *)of;
751
752 if (!muxer->fc)
753 continue;
754
755 sec_ctx.context_id = av_asprintf("Output_%d", n);
756
757 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
758
759 av_freep(&sec_ctx.context_id);
760
761 ////print_str_opt("index", av_get_media_type_string(of->index));
762 print_fmt("index", "%d", of->index);
763 ////print_str("url", of->url);
764 print_str("muxer_name", muxer->fc->oformat->name);
765 if (of->url) {
766 char *extension = get_extension(of->url);
767 if (extension) {
768 print_str("file_extension", extension);
769 av_freep(&extension);
770 }
771 print_str("url", of->url);
772 }
773
774 sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
775
776 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
777
778 av_freep(&sec_ctx.context_id);
779
780 for (int i = 0; i < of->nb_streams; i++) {
781 OutputStream *ost = of->streams[i];
782 const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
783
784 sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
785 sec_ctx.context_type = av_get_media_type_string(ost->type);
786 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
787 av_freep(&sec_ctx.context_id);
788 sec_ctx.context_type = NULL;
789
790 av_bprint_clear(&buf);
791
792 print_fmt("id", "r_out__%d_%d", n, i);
793
794 if (codec_desc && codec_desc->name) {
795 av_bprintf(&buf, "%s", codec_desc->long_name);
796 } else {
797 av_bprintf(&buf, "%s", "unknown");
798 }
799
800 print_fmt("name", "%s", buf.str);
801 print_fmt("index", "%d", ost->index);
802
803 print_str_opt("media_type", av_get_media_type_string(ost->type));
804
805 avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
806 }
807
808 avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
809 avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
810 }
811
812 avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
813
814
815 avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
816
817 for (int n = 0; n < nb_ofiles; n++) {
818 OutputFile *of = ofiles[n];
819
820 for (int i = 0; i < of->nb_streams; i++) {
821 OutputStream *ost = of->streams[i];
822
823 if (ost->ist && !ost->filter) {
824 sec_ctx.context_type = av_get_media_type_string(ost->type);
825 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_STREAMLINK);
826 sec_ctx.context_type = NULL;
827
828 if (ost->enc) {
829 print_fmt("dest_stream_id", "out__%d_%d", n, i);
830 print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
831 print_str("operation", "Transcode");
832 } else {
833 print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
834 print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
835 print_str("operation", "Stream Copy");
836 }
837
838 print_str_opt("media_type", av_get_media_type_string(ost->type));
839
840 avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
841 }
842 }
843 }
844
845 avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
846
847 av_bprint_finalize(&buf, NULL);
848 return 0;
849 }
850
851
852 static void uninit_graphprint(GraphPrintContext *gpc)
853 {
854 if (gpc->tfc)
855 avtext_context_close(&gpc->tfc);
856
857 if (gpc->wctx)
858 avtextwriter_context_close(&gpc->wctx);
859
860 // Finalize the print buffer if it was initialized
861 av_bprint_finalize(&gpc->pbuf, NULL);
862
863 av_freep(&gpc);
864 }
865
866 static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
867 {
868 const AVTextFormatter *text_formatter;
869 AVTextFormatContext *tfc = NULL;
870 AVTextWriterContext *wctx = NULL;
871 GraphPrintContext *gpc = NULL;
872 int ret;
873
874 *pgpc = NULL;
875
876 av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
877
878 const char *w_name = print_graphs_format ? print_graphs_format : "json";
879
880 text_formatter = avtext_get_formatter_by_name(w_name);
881 if (!text_formatter) {
882 av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
883 ret = AVERROR(EINVAL);
884 goto fail;
885 }
886
887 ret = avtextwriter_create_buffer(&wctx, target_buf);
888 if (ret < 0) {
889 av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
890 ret = AVERROR(EINVAL);
891 goto fail;
892 }
893
894 AVTextFormatOptions tf_options = { .show_optional_fields = -1 };
895 const char *w_args = print_graphs_format ? strchr(print_graphs_format, '=') : NULL;
896 if (w_args)
897 ++w_args; // consume '='
898 ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), tf_options, NULL);
899 if (ret < 0) {
900 goto fail;
901 }
902
903 gpc = av_mallocz(sizeof(GraphPrintContext));
904 if (!gpc) {
905 ret = AVERROR(ENOMEM);
906 goto fail;
907 }
908
909 gpc->wctx = wctx;
910 gpc->tfc = tfc;
911 av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
912
913 gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
914 gpc->is_diagram = !!(tfc->formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
915 if (gpc->is_diagram) {
916 tfc->opts.show_value_unit = 1;
917 tfc->opts.show_optional_fields = -1;
918 gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
919 gpc->skip_buffer_filters = 1;
920 ////} else {
921 //// gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
922 }
923
924 if (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml")) {
925 gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
926
927 if (!strcmp(text_formatter->name, "mermaidhtml"))
928 gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
929
930 av_diagram_init(tfc, &gpc->diagram_config);
931 }
932
933 *pgpc = gpc;
934
935 return 0;
936
937 fail:
938 if (tfc)
939 avtext_context_close(&tfc);
940 if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
941 avtextwriter_context_close(&wctx);
942 av_freep(&gpc);
943
944 return ret;
945 }
946
947
948 int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
949 {
950 av_assert2(fg);
951
952 GraphPrintContext *gpc = NULL;
953 AVTextFormatContext *tfc;
954 AVBPrint *target_buf = &fg->graph_print_buf;
955 int ret;
956
957 if (target_buf->len)
958 av_bprint_finalize(target_buf, NULL);
959
960 ret = init_graphprint(&gpc, target_buf);
961 if (ret)
962 return ret;
963
964 tfc = gpc->tfc;
965
966 // Due to the threading model each graph needs to print itself into a buffer
967 // from its own thread. The actual printing happens short before cleanup in ffmpeg.c
968 // where all graphs are assembled together. To make this work, we need to put the
969 // formatting context into the same state like it would be when printing all at once,
970 // so here we print the section headers and clear the buffer to get into the right state.
971 avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
972 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
973 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
974
975 av_bprint_clear(target_buf);
976
977 print_filtergraph_single(gpc, fg, graph);
978
979 if (gpc->is_diagram) {
980 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
981 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
982 }
983
984 uninit_graphprint(gpc);
985
986 return 0;
987 }
988
989 static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
990 {
991 GraphPrintContext *gpc = NULL;
992 AVTextFormatContext *tfc;
993 AVBPrint target_buf;
994 int ret;
995
996 ret = init_graphprint(&gpc, &target_buf);
997 if (ret)
998 goto cleanup;
999
1000 tfc = gpc->tfc;
1001
1002 avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
1003 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
1004
1005 for (int i = 0; i < nb_graphs; i++) {
1006 AVBPrint *graph_buf = &graphs[i]->graph_print_buf;
1007
1008 if (graph_buf->len > 0) {
1009 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
1010 av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
1011 av_bprint_finalize(graph_buf, NULL);
1012 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
1013 }
1014 }
1015
1016 for (int n = 0; n < nb_ofiles; n++) {
1017 OutputFile *of = ofiles[n];
1018
1019 for (int i = 0; i < of->nb_streams; i++) {
1020 OutputStream *ost = of->streams[i];
1021
1022 if (ost->fg_simple) {
1023 AVBPrint *graph_buf = &ost->fg_simple->graph_print_buf;
1024
1025 if (graph_buf->len > 0) {
1026 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
1027 av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
1028 av_bprint_finalize(graph_buf, NULL);
1029 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
1030 }
1031 }
1032 }
1033 }
1034
1035 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
1036
1037 print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
1038
1039 avtext_print_section_footer(tfc); // SECTION_ID_ROOT
1040
1041 if (print_graphs_file) {
1042 AVIOContext *avio = NULL;
1043
1044 if (!strcmp(print_graphs_file, "-")) {
1045 printf("%s", target_buf.str);
1046 } else {
1047 ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
1048 if (ret < 0) {
1049 av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
1050 goto cleanup;
1051 }
1052
1053 avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
1054
1055 if ((ret = avio_closep(&avio)) < 0)
1056 av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
1057 }
1058 }
1059
1060 if (print_graphs)
1061 av_log(NULL, AV_LOG_INFO, "%s %c", target_buf.str, '\n');
1062
1063 cleanup:
1064 // Properly clean up resources
1065 if (gpc)
1066 uninit_graphprint(gpc);
1067
1068 // Ensure the target buffer is properly finalized
1069 av_bprint_finalize(&target_buf, NULL);
1070
1071 return ret;
1072 }
1073
1074 int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
1075 {
1076 int ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
1077 ff_resman_uninit();
1078 return ret;
1079 }
1080