FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/graph/graphprint.c
Date: 2026-10-17 12:12:06
Exec Total Coverage
Lines: 469 635 73.9%
Functions: 25 29 86.2%
Branches: 190 364 52.2%

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 /** record of the print calls, used instead of a formatter context */
128 AVBPrint *rec;
129
130 int id_prefix_num;
131 int is_diagram;
132 int opt_flags;
133 int show_value_unit;
134 int skip_buffer_filters;
135 AVBPrint pbuf;
136
137 } GraphPrintContext;
138
139 enum GraphPrintOp {
140 GP_OP_SECTION_HEADER,
141 GP_OP_SECTION_FOOTER,
142 GP_OP_STRING,
143 GP_OP_INTEGER,
144 GP_OP_UNIT_INTEGER,
145 };
146
147 695 static void rec_write(AVBPrint *rec, const void *data, size_t size)
148 {
149 695 av_bprint_append_data(rec, data, size);
150 695 }
151
152 189 static void rec_write_str(AVBPrint *rec, const char *str)
153 {
154
2/2
✓ Branch 0 taken 176 times.
✓ Branch 1 taken 13 times.
189 uint32_t len = str ? strlen(str) + 1 : 0;
155
156 189 rec_write(rec, &len, sizeof(len));
157
2/2
✓ Branch 0 taken 176 times.
✓ Branch 1 taken 13 times.
189 if (len)
158 176 rec_write(rec, str, len);
159 189 }
160
161 519 static int rec_read(const uint8_t **p, const uint8_t *end, void *data, size_t size)
162 {
163
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 519 times.
519 if ((size_t)(end - *p) < size)
164 ✗ return AVERROR_INVALIDDATA;
165 519 memcpy(data, *p, size);
166 519 *p += size;
167 519 return 0;
168 }
169
170 189 static int rec_read_str(const uint8_t **p, const uint8_t *end, const char **str)
171 {
172 uint32_t len;
173 189 int ret = rec_read(p, end, &len, sizeof(len));
174
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 189 times.
189 if (ret < 0)
176 ✗ return ret;
177
4/6
✓ Branch 0 taken 189 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 176 times.
✓ Branch 3 taken 13 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 176 times.
189 if ((size_t)(end - *p) < len || (len && (*p)[len - 1]))
178 ✗ return AVERROR_INVALIDDATA;
179
2/2
✓ Branch 0 taken 176 times.
✓ Branch 1 taken 13 times.
189 *str = len ? (const char *)*p : NULL;
180 189 *p += len;
181 189 return 0;
182 }
183
184 39 static void gp_section_header(const GraphPrintContext *gpc, const AVTextFormatSectionContext *sec_ctx, int section_id)
185 {
186
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 15 times.
39 if (gpc->rec) {
187 24 uint8_t op = GP_OP_SECTION_HEADER, has_ctx = !!sec_ctx;
188 24 int32_t id = section_id;
189
190 24 rec_write(gpc->rec, &op, sizeof(op));
191 24 rec_write(gpc->rec, &id, sizeof(id));
192 24 rec_write(gpc->rec, &has_ctx, sizeof(has_ctx));
193
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 11 times.
24 if (sec_ctx) {
194 13 int32_t flags = sec_ctx->context_flags;
195
196 13 rec_write_str(gpc->rec, sec_ctx->context_id);
197 13 rec_write_str(gpc->rec, sec_ctx->context_type);
198 13 rec_write(gpc->rec, &flags, sizeof(flags));
199 }
200 24 return;
201 }
202 15 avtext_print_section_header(gpc->tfc, sec_ctx, section_id);
203 }
204
205 39 static void gp_section_footer(const GraphPrintContext *gpc)
206 {
207
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 15 times.
39 if (gpc->rec) {
208 24 uint8_t op = GP_OP_SECTION_FOOTER;
209
210 24 rec_write(gpc->rec, &op, sizeof(op));
211 24 return;
212 }
213 15 avtext_print_section_footer(gpc->tfc);
214 }
215
216 90 static void gp_print_string(const GraphPrintContext *gpc, const char *key, const char *value, int flags)
217 {
218
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 23 times.
90 if (gpc->rec) {
219 67 uint8_t op = GP_OP_STRING;
220 67 int32_t f = flags;
221
222 67 rec_write(gpc->rec, &op, sizeof(op));
223 67 rec_write(gpc->rec, &f, sizeof(f));
224 67 rec_write_str(gpc->rec, key);
225 67 rec_write_str(gpc->rec, value);
226 67 return;
227 }
228 23 avtext_print_string(gpc->tfc, key, value, flags);
229 }
230
231 29 static void gp_print_integer(const GraphPrintContext *gpc, const char *key, int64_t value, int flags)
232 {
233
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 if (gpc->rec) {
234 29 uint8_t op = GP_OP_INTEGER;
235 29 int32_t f = flags;
236
237 29 rec_write(gpc->rec, &op, sizeof(op));
238 29 rec_write(gpc->rec, &f, sizeof(f));
239 29 rec_write_str(gpc->rec, key);
240 29 rec_write(gpc->rec, &value, sizeof(value));
241 29 return;
242 }
243 ✗ avtext_print_integer(gpc->tfc, key, value, flags);
244 }
245
246 ✗ static void gp_print_unit_integer(const GraphPrintContext *gpc, const char *key, int64_t value, const char *unit)
247 {
248 ✗ if (gpc->rec) {
249 ✗ uint8_t op = GP_OP_UNIT_INTEGER;
250
251 ✗ rec_write(gpc->rec, &op, sizeof(op));
252 ✗ rec_write_str(gpc->rec, key);
253 ✗ rec_write(gpc->rec, &value, sizeof(value));
254 ✗ rec_write_str(gpc->rec, unit);
255 ✗ return;
256 }
257 ✗ avtext_print_unit_integer(gpc->tfc, key, value, AV_TEXTFORMAT_VALUE_FMT_INT, unit);
258 }
259
260 6 static void gp_print_rational(const GraphPrintContext *gpc, const char *key, AVRational q, char sep)
261 {
262 char buf[44];
263
264 6 snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
265 6 gp_print_string(gpc, key, buf, 0);
266 6 }
267
268 1 static int gp_replay(const GraphPrintContext *gpc, const AVBPrint *rec)
269 {
270 1 const uint8_t *p = (const uint8_t *)rec->str;
271 1 const uint8_t *end = p + FFMIN(rec->len, rec->size - 1);
272 int ret;
273
274
2/2
✓ Branch 0 taken 144 times.
✓ Branch 1 taken 1 times.
145 while (p < end) {
275 144 AVTextFormatSectionContext sec_ctx = { 0 };
276 const char *key, *str;
277 uint8_t op, has_ctx;
278 int32_t id, flags;
279 int64_t value;
280
281
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 144 times.
144 if ((ret = rec_read(&p, end, &op, sizeof(op))) < 0)
282 ✗ return ret;
283
284
4/6
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 67 times.
✓ Branch 3 taken 29 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
144 switch (op) {
285 24 case GP_OP_SECTION_HEADER:
286
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
48 if ((ret = rec_read(&p, end, &id, sizeof(id))) < 0 ||
287 24 (ret = rec_read(&p, end, &has_ctx, sizeof(has_ctx))) < 0)
288 ✗ return ret;
289
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 11 times.
24 if (has_ctx) {
290
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✗ Branch 4 not taken.
26 if ((ret = rec_read_str(&p, end, &str)) < 0 ||
291
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
26 (ret = rec_read_str(&p, end, &sec_ctx.context_type)) < 0 ||
292 13 (ret = rec_read(&p, end, &flags, sizeof(flags))) < 0)
293 ✗ return ret;
294 13 sec_ctx.context_id = (char *)str;
295 13 sec_ctx.context_flags = flags;
296 }
297
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 11 times.
24 avtext_print_section_header(gpc->tfc, has_ctx ? &sec_ctx : NULL, id);
298 24 break;
299 24 case GP_OP_SECTION_FOOTER:
300 24 avtext_print_section_footer(gpc->tfc);
301 24 break;
302 67 case GP_OP_STRING:
303
2/4
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
✗ Branch 4 not taken.
134 if ((ret = rec_read(&p, end, &flags, sizeof(flags))) < 0 ||
304
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 67 times.
134 (ret = rec_read_str(&p, end, &key)) < 0 ||
305 67 (ret = rec_read_str(&p, end, &str)) < 0)
306 ✗ return ret;
307 67 avtext_print_string(gpc->tfc, key, str, flags);
308 67 break;
309 29 case GP_OP_INTEGER:
310
2/4
✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
✗ Branch 4 not taken.
58 if ((ret = rec_read(&p, end, &flags, sizeof(flags))) < 0 ||
311
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
58 (ret = rec_read_str(&p, end, &key)) < 0 ||
312 29 (ret = rec_read(&p, end, &value, sizeof(value))) < 0)
313 ✗ return ret;
314 29 avtext_print_integer(gpc->tfc, key, value, flags);
315 29 break;
316 ✗ case GP_OP_UNIT_INTEGER:
317 ✗ if ((ret = rec_read_str(&p, end, &key)) < 0 ||
318 ✗ (ret = rec_read(&p, end, &value, sizeof(value))) < 0 ||
319 ✗ (ret = rec_read_str(&p, end, &str)) < 0)
320 ✗ return ret;
321 ✗ avtext_print_unit_integer(gpc->tfc, key, value, AV_TEXTFORMAT_VALUE_FMT_INT, str);
322 ✗ break;
323 ✗ default:
324 ✗ return AVERROR_INVALIDDATA;
325 }
326 }
327
328 1 return 0;
329 }
330
331 /* Text Format API Shortcuts */
332 #define print_id(k, v) print_sanizied_id(gpc, k, v, 0)
333 #define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
334 #define print_int(k, v) gp_print_integer(gpc, k, v, 0)
335 #define print_int_opt(k, v) gp_print_integer(gpc, k, v, gpc->opt_flags)
336 #define print_q(k, v, s) gp_print_rational(gpc, k, v, s)
337 #define print_str(k, v) gp_print_string(gpc, k, v, 0)
338 #define print_str_opt(k, v) gp_print_string(gpc, k, v, gpc->opt_flags)
339 #define print_val(k, v, u) gp_print_unit_integer(gpc, k, v, u)
340
341 #define print_fmt(k, f, ...) do { \
342 av_bprint_clear(&gpc->pbuf); \
343 av_bprintf(&gpc->pbuf, f, __VA_ARGS__); \
344 gp_print_string(gpc, k, gpc->pbuf.str, 0); \
345 } while (0)
346
347 #define print_fmt_opt(k, f, ...) do { \
348 av_bprint_clear(&gpc->pbuf); \
349 av_bprintf(&gpc->pbuf, f, __VA_ARGS__); \
350 gp_print_string(gpc, k, gpc->pbuf.str, gpc->opt_flags); \
351 } while (0)
352
353
354 static atomic_int prefix_num = 0;
355
356 ✗ static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
357 {
358 unsigned i;
359 ✗ for (i = 0; src[i] && i < dst_size - 1; i++)
360 ✗ dst[i] = (char)av_toupper(src[i]);
361 ✗ dst[i] = 0;
362 ✗ return dst;
363 }
364
365 2 static char *get_extension(const char *url)
366 {
367 2 const char *dot = NULL;
368 2 const char *sep = NULL;
369 const char *end;
370
371
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!url)
372 ✗ return NULL;
373
374 /* Stop at the first query ('?') or fragment ('#') delimiter so they
375 * are not considered part of the path. */
376 2 end = strpbrk(url, "?#");
377
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!end)
378 2 end = url + strlen(url);
379
380 /* Scan the path component only. */
381
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 2 times.
35 for (const char *p = url; p < end; p++) {
382
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 32 times.
33 if (*p == '.')
383 1 dot = p;
384
2/4
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 32 times.
32 else if (*p == '/' || *p == '\\')
385 ✗ sep = p;
386 }
387
388 /* Validate that we have a proper extension. */
389
5/10
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
2 if (dot && dot != url && (!sep || dot > sep + 1) && (dot + 1) < end) {
390 /* Use FFmpeg helper to duplicate the substring. */
391 1 return av_strndup(dot + 1, end - (dot + 1));
392 }
393
394 1 return NULL;
395 }
396
397 ✗ static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
398 {
399
400 ✗ if (!hw_device_context)
401 ✗ return;
402
403 ✗ print_int_opt("has_hw_device_context", 1);
404 ✗ print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
405 }
406
407 ✗ static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
408 {
409 const AVPixFmtDescriptor *pix_desc_hw;
410 const AVPixFmtDescriptor *pix_desc_sw;
411
412 ✗ if (!hw_frames_context || !hw_frames_context->device_ctx)
413 ✗ return;
414
415 ✗ gp_section_header(gpc, NULL, SECTION_ID_HWFRAMESCONTEXT);
416
417 ✗ print_int_opt("has_hw_frames_context", 1);
418 ✗ print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
419
420 ✗ pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
421 ✗ if (pix_desc_hw) {
422 ✗ print_str("hw_pixel_format", pix_desc_hw->name);
423 ✗ if (pix_desc_hw->alias)
424 ✗ print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
425 }
426
427 ✗ pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
428 ✗ if (pix_desc_sw) {
429 ✗ print_str("sw_pixel_format", pix_desc_sw->name);
430 ✗ if (pix_desc_sw->alias)
431 ✗ print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
432 }
433
434 ✗ print_int_opt("width", hw_frames_context->width);
435 ✗ print_int_opt("height", hw_frames_context->height);
436 ✗ print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
437
438 ✗ gp_section_footer(gpc); // SECTION_ID_HWFRAMESCONTEXT
439 }
440
441 6 static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
442 {
443 AVBufferRef *hw_frames_ctx;
444 char layout_string[64];
445
446
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!link)
447 ✗ return;
448
449 6 hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
450
451 6 print_str_opt("media_type", av_get_media_type_string(link->type));
452
453
1/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
6 switch (link->type) {
454 6 case AVMEDIA_TYPE_VIDEO:
455
456
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
6 if (hw_frames_ctx && hw_frames_ctx->data) {
457 ✗ AVHWFramesContext * hwfctx = (AVHWFramesContext *)hw_frames_ctx->data;
458 ✗ const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
459 ✗ const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
460 ✗ if (pix_desc_hw && pix_desc_sw)
461 ✗ print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
462 } else {
463 6 print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
464 }
465
466
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 if (link->w && link->h) {
467
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (gpc->show_value_unit) {
468 ✗ print_fmt("size", "%dx%d", link->w, link->h);
469 } else {
470 6 print_int("width", link->w);
471 6 print_int("height", link->h);
472 }
473 }
474
475 6 print_q("sar", link->sample_aspect_ratio, ':');
476
477
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
478 ✗ print_str_opt("color_range", av_color_range_name(link->color_range));
479
480
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
481 ✗ print_str("color_space", av_color_space_name(link->colorspace));
482 6 break;
483
484 ✗ case AVMEDIA_TYPE_SUBTITLE:
485 ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
486
487 ✗ if (link->w && link->h) {
488 ✗ if (gpc->show_value_unit) {
489 ✗ print_fmt("size", "%dx%d", link->w, link->h);
490 } else {
491 ✗ print_int("width", link->w);
492 ✗ print_int("height", link->h);
493 }
494 }
495
496 ✗ break;
497
498 ✗ case AVMEDIA_TYPE_AUDIO:
499 ✗ av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
500 ✗ print_str("channel_layout", layout_string);
501 ✗ print_val("channels", link->ch_layout.nb_channels, "ch");
502 ✗ if (gpc->show_value_unit)
503 ✗ print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
504 else
505 ✗ print_val("sample_rate", link->sample_rate, "Hz");
506
507 ✗ break;
508 }
509
510 6 print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
511
512
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
6 if (hw_frames_ctx && hw_frames_ctx->data)
513 ✗ print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
514 6 av_buffer_unref(&hw_frames_ctx);
515 }
516
517 320 static char sanitize_char(const char c)
518 {
519
11/12
✓ Branch 0 taken 301 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 273 times.
✓ Branch 3 taken 28 times.
✓ Branch 4 taken 220 times.
✓ Branch 5 taken 72 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 220 times.
✓ Branch 8 taken 45 times.
✓ Branch 9 taken 27 times.
✓ Branch 10 taken 18 times.
✓ Branch 11 taken 27 times.
320 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
520 266 return c;
521 54 return '_';
522 }
523
524 14 static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
525 {
526 AVBPrint buf;
527
528
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
14 if (!key || !id_str)
529 ✗ return;
530
531 14 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
532
533
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
14 if (!skip_prefix)
534 12 av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
535
536 // sanizize section id
537
2/2
✓ Branch 0 taken 204 times.
✓ Branch 1 taken 14 times.
218 for (const char *p = id_str; *p; p++)
538 204 av_bprint_chars(&buf, sanitize_char(*p), 1);
539
540 14 print_str(key, buf.str);
541
542 14 av_bprint_finalize(&buf, NULL);
543 }
544
545 10 static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
546 {
547 10 AVTextFormatSectionContext sec_ctx = { 0 };
548 AVBPrint buf;
549
550
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!id_str)
551 ✗ return;
552
553 10 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
554
555
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (!skip_prefix)
556 10 av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
557
558 // sanizize section id
559
2/2
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 10 times.
126 for (const char *p = id_str; *p; p++)
560 116 av_bprint_chars(&buf, sanitize_char(*p), 1);
561
562 10 sec_ctx.context_id = buf.str;
563
564 10 gp_section_header(gpc, &sec_ctx, section_id);
565
566 10 av_bprint_finalize(&buf, NULL);
567 }
568
569 12 static const char *get_filterpad_name(const AVFilterPad *pad)
570 {
571
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 return pad ? avfilter_pad_get_name(pad, 0) : "pad";
572 }
573
574 4 static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
575 {
576 4 AVTextFormatSectionContext sec_ctx = { 0 };
577
578 4 print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
579
580 ////print_id("filter_id", filter->name);
581
582
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (filter->filter) {
583 4 print_str("filter_name", filter->filter->name);
584 4 print_str_opt("description", filter->filter->description);
585 4 print_int_opt("nb_inputs", filter->nb_inputs);
586 4 print_int_opt("nb_outputs", filter->nb_outputs);
587 }
588
589
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (filter->hw_device_ctx) {
590 ✗ AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
591 ✗ print_hwdevicecontext(gpc, device_context);
592 ✗ if (filter->extra_hw_frames > 0)
593 ✗ print_int("extra_hw_frames", filter->extra_hw_frames);
594 }
595
596 4 gp_section_header(gpc, NULL, SECTION_ID_FILTER_INPUTS);
597
598
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 for (unsigned i = 0; i < filter->nb_inputs; i++) {
599 AVDictionaryEntry *dic_entry;
600 3 AVFilterLink *link = filter->inputs[i];
601
602 3 sec_ctx.context_type = av_get_media_type_string(link->type);
603 3 gp_section_header(gpc, &sec_ctx, SECTION_ID_FILTER_INPUT);
604 3 sec_ctx.context_type = NULL;
605
606 3 print_int_opt("input_index", i);
607 3 print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
608
609 3 dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
610
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (dic_entry) {
611 char buf[256];
612 1 (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
613 1 print_id_noprefix("source_filter_id", buf);
614 } else {
615 2 print_id("source_filter_id", link->src->name);
616 }
617
618 3 print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
619 3 print_id("filter_id", filter->name);
620
621 3 print_link(gpc, link);
622
623 3 gp_section_footer(gpc); // SECTION_ID_FILTER_INPUT
624 }
625
626 4 gp_section_footer(gpc); // SECTION_ID_FILTER_INPUTS
627
628 4 gp_section_header(gpc, NULL, SECTION_ID_FILTER_OUTPUTS);
629
630
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 for (unsigned i = 0; i < filter->nb_outputs; i++) {
631 AVDictionaryEntry *dic_entry;
632 3 AVFilterLink *link = filter->outputs[i];
633 char buf[256];
634
635 3 sec_ctx.context_type = av_get_media_type_string(link->type);
636 3 gp_section_header(gpc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
637 3 sec_ctx.context_type = NULL;
638
639 3 dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
640
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (dic_entry) {
641 1 (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
642 1 print_id_noprefix("dest_filter_id", buf);
643 } else {
644 2 print_id("dest_filter_id", link->dst->name);
645 }
646
647 3 print_int_opt("output_index", i);
648 3 print_str_opt("pad_name", get_filterpad_name(link->srcpad));
649 ////print_id("dest_filter_id", link->dst->name);
650 3 print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
651 3 print_id("filter_id", filter->name);
652
653 3 print_link(gpc, link);
654
655 3 gp_section_footer(gpc); // SECTION_ID_FILTER_OUTPUT
656 }
657
658 4 gp_section_footer(gpc); // SECTION_ID_FILTER_OUTPUTS
659
660 4 gp_section_footer(gpc); // SECTION_ID_FILTER
661 4 }
662
663 1 static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
664 {
665 1 AVDictionary *input_map = NULL;
666 1 AVDictionary *output_map = NULL;
667
668 1 print_int("graph_index", fg->index);
669 1 print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
670 1 print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
671 1 print_str("description", fg->graph_desc);
672
673 1 print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
674
675
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < fg->nb_inputs; i++) {
676 1 InputFilter *ifilter = fg->inputs[i];
677 1 enum AVMediaType media_type = ifilter->type;
678
679 1 gp_section_header(gpc, NULL, SECTION_ID_GRAPH_INPUT);
680
681 1 print_int("input_index", ifilter->index);
682
683
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (ifilter->linklabel)
684 1 print_str("link_label", (const char*)ifilter->linklabel);
685
686
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (ifilter->filter) {
687 1 print_id("filter_id", ifilter->filter->name);
688 1 print_str("filter_name", ifilter->filter->filter->name);
689 }
690
691
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (ifilter->linklabel && ifilter->filter)
692 1 av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
693 ✗ else if (ifilter->input_name && ifilter->filter)
694 ✗ av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->input_name, 0);
695
696 1 print_str("media_type", av_get_media_type_string(media_type));
697
698 1 gp_section_footer(gpc); // SECTION_ID_GRAPH_INPUT
699 }
700
701 1 gp_section_footer(gpc); // SECTION_ID_GRAPH_INPUTS
702
703 1 print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
704
705
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < fg->nb_outputs; i++) {
706 1 OutputFilter *ofilter = fg->outputs[i];
707
708 1 gp_section_header(gpc, NULL, SECTION_ID_GRAPH_OUTPUT);
709
710 1 print_int("output_index", ofilter->index);
711
712 1 print_str("name", ofilter->output_name);
713
714
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (fg->outputs[i]->linklabel)
715 ✗ print_str("link_label", (const char*)fg->outputs[i]->linklabel);
716
717
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (ofilter->filter) {
718 1 print_id("filter_id", ofilter->filter->name);
719 1 print_str("filter_name", ofilter->filter->filter->name);
720 }
721
722
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (ofilter->output_name && ofilter->filter)
723 1 av_dict_set(&output_map, ofilter->filter->name, ofilter->output_name, 0);
724
725
726 1 print_str("media_type", av_get_media_type_string(ofilter->type));
727
728 1 gp_section_footer(gpc); // SECTION_ID_GRAPH_OUTPUT
729 }
730
731 1 gp_section_footer(gpc); // SECTION_ID_GRAPH_OUTPUTS
732
733
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (graph) {
734 1 AVTextFormatSectionContext sec_ctx = { 0 };
735
736 1 sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
737
738 1 gp_section_header(gpc, &sec_ctx, SECTION_ID_FILTERS);
739
740
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (gpc->is_diagram) {
741 ✗ print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
742 ✗ print_str("description", fg->graph_desc);
743 ✗ print_str("id", sec_ctx.context_id);
744 }
745
746 1 av_freep(&sec_ctx.context_id);
747
748
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 for (unsigned i = 0; i < graph->nb_filters; i++) {
749 4 AVFilterContext *filter = graph->filters[i];
750
751
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (gpc->skip_buffer_filters) {
752 ✗ if (av_dict_get(input_map, filter->name, NULL, 0))
753 ✗ continue;
754 ✗ if (av_dict_get(output_map, filter->name, NULL, 0))
755 ✗ continue;
756 }
757
758 4 sec_ctx.context_id = filter->name;
759
760 4 print_filter(gpc, filter, input_map, output_map);
761 }
762
763 1 gp_section_footer(gpc); // SECTION_ID_FILTERS
764 }
765
766 // Clean up dictionaries
767 1 av_dict_free(&input_map);
768 1 av_dict_free(&output_map);
769 1 }
770
771 1 static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
772 {
773 AVBPrint buf;
774 1 AVTextFormatSectionContext sec_ctx = { 0 };
775
776 1 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
777
778 1 print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
779
780
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int n = nb_ifiles - 1; n >= 0; n--) {
781 1 InputFile *ifi = ifiles[n];
782 1 AVFormatContext *fc = ifi->ctx;
783
784 1 sec_ctx.context_id = av_asprintf("Input_%d", n);
785 1 gp_section_header(gpc, &sec_ctx, SECTION_ID_INPUTFILE);
786 1 av_freep(&sec_ctx.context_id);
787
788 1 print_fmt("index", "%d", ifi->index);
789
790
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fc) {
791 1 print_str("demuxer_name", fc->iformat->name);
792
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (fc->url) {
793 1 char *extension = get_extension(fc->url);
794
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (extension) {
795 1 print_str("file_extension", extension);
796 1 av_freep(&extension);
797 }
798 1 print_str("url", fc->url);
799 }
800 }
801
802 1 sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
803
804 1 gp_section_header(gpc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
805
806 1 av_freep(&sec_ctx.context_id);
807
808
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < ifi->nb_streams; i++) {
809 1 InputStream *ist = ifi->streams[i];
810 const AVCodecDescriptor *codec_desc;
811
812
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!ist || !ist->par)
813 ✗ continue;
814
815 1 codec_desc = avcodec_descriptor_get(ist->par->codec_id);
816
817 1 sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
818
819 1 sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
820
821 1 gp_section_header(gpc, &sec_ctx, SECTION_ID_INPUTSTREAM);
822 1 av_freep(&sec_ctx.context_id);
823 1 sec_ctx.context_type = NULL;
824
825 1 av_bprint_clear(&buf);
826
827 1 print_fmt("id", "r_in_%d_%d", n, i);
828
829
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (codec_desc && codec_desc->name) {
830 ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
831 1 av_bprintf(&buf, "%s", codec_desc->long_name);
832 ✗ } else if (ist->dec) {
833 char char_buf[256];
834 ✗ av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
835 ✗ } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
836 ✗ av_bprintf(&buf, "%s", "Attachment");
837 ✗ } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
838 ✗ av_bprintf(&buf, "%s", "Data");
839 }
840
841 1 print_fmt("name", "%s", buf.str);
842 1 print_fmt("index", "%d", ist->index);
843
844
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (ist->dec)
845 1 print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
846
847 1 gp_section_footer(gpc); // SECTION_ID_INPUTSTREAM
848 }
849
850 1 gp_section_footer(gpc); // SECTION_ID_INPUTSTREAMS
851 1 gp_section_footer(gpc); // SECTION_ID_INPUTFILE
852 }
853
854 1 gp_section_footer(gpc); // SECTION_ID_INPUTFILES
855
856
857 1 print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
858
859
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int n = 0; n < nb_ifiles; n++) {
860 1 InputFile *ifi = ifiles[n];
861
862
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < ifi->nb_streams; i++) {
863 1 InputStream *ist = ifi->streams[i];
864
865
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!ist->decoder)
866 ✗ continue;
867
868 1 sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
869 1 sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
870 1 sec_ctx.context_flags = 2;
871
872 1 gp_section_header(gpc, &sec_ctx, SECTION_ID_DECODER);
873 1 av_freep(&sec_ctx.context_id);
874 1 sec_ctx.context_type = NULL;
875 1 sec_ctx.context_flags = 0;
876
877 1 av_bprint_clear(&buf);
878
879 1 print_fmt("source_id", "r_in_%d_%d", n, i);
880 1 print_fmt("id", "in_%d_%d", n, i);
881
882 ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
883 1 print_fmt("name", "%s", ist->dec->name);
884
885 1 print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
886
887 1 gp_section_footer(gpc); // SECTION_ID_DECODER
888 }
889 }
890
891 1 gp_section_footer(gpc); // SECTION_ID_DECODERS
892
893
894 1 print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
895
896
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int n = 0; n < nb_ofiles; n++) {
897 1 OutputFile *of = ofiles[n];
898
899
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < of->nb_streams; i++) {
900 1 OutputStream *ost = of->streams[i];
901 ////const AVCodecDescriptor *codec_desc;
902
903
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
1 if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
904 ✗ continue;
905
906 ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
907
908 1 sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
909 1 sec_ctx.context_type = av_get_media_type_string(ost->type);
910 1 sec_ctx.context_flags = 2;
911
912 1 gp_section_header(gpc, &sec_ctx, SECTION_ID_ENCODER);
913 1 av_freep(&sec_ctx.context_id);
914 1 sec_ctx.context_type = NULL;
915 1 sec_ctx.context_flags = 0;
916
917 1 av_bprint_clear(&buf);
918
919 1 print_fmt("id", "out__%d_%d", n, i);
920 1 print_fmt("dest_id", "r_out__%d_%d", n, i);
921
922 1 print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
923
924 1 print_str_opt("media_type", av_get_media_type_string(ost->type));
925
926 1 gp_section_footer(gpc); // SECTION_ID_ENCODER
927 }
928 }
929
930 1 gp_section_footer(gpc); // SECTION_ID_ENCODERS
931
932
933 1 print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
934
935
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int n = nb_ofiles - 1; n >= 0; n--) {
936 1 OutputFile *of = ofiles[n];
937 1 Muxer *muxer = (Muxer *)of;
938
939
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!muxer->fc)
940 ✗ continue;
941
942 1 sec_ctx.context_id = av_asprintf("Output_%d", n);
943
944 1 gp_section_header(gpc, &sec_ctx, SECTION_ID_OUTPUTFILE);
945
946 1 av_freep(&sec_ctx.context_id);
947
948 ////print_str_opt("index", av_get_media_type_string(of->index));
949 1 print_fmt("index", "%d", of->index);
950 ////print_str("url", of->url);
951 1 print_str("muxer_name", muxer->fc->oformat->name);
952
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (of->url) {
953 1 char *extension = get_extension(of->url);
954
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (extension) {
955 ✗ print_str("file_extension", extension);
956 ✗ av_freep(&extension);
957 }
958 1 print_str("url", of->url);
959 }
960
961 1 sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
962
963 1 gp_section_header(gpc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
964
965 1 av_freep(&sec_ctx.context_id);
966
967
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < of->nb_streams; i++) {
968 1 OutputStream *ost = of->streams[i];
969 1 const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
970
971 1 sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
972 1 sec_ctx.context_type = av_get_media_type_string(ost->type);
973 1 gp_section_header(gpc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
974 1 av_freep(&sec_ctx.context_id);
975 1 sec_ctx.context_type = NULL;
976
977 1 av_bprint_clear(&buf);
978
979 1 print_fmt("id", "r_out__%d_%d", n, i);
980
981
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (codec_desc && codec_desc->name) {
982 1 av_bprintf(&buf, "%s", codec_desc->long_name);
983 } else {
984 ✗ av_bprintf(&buf, "%s", "unknown");
985 }
986
987 1 print_fmt("name", "%s", buf.str);
988 1 print_fmt("index", "%d", ost->index);
989
990 1 print_str_opt("media_type", av_get_media_type_string(ost->type));
991
992 1 gp_section_footer(gpc); // SECTION_ID_OUTPUTSTREAM
993 }
994
995 1 gp_section_footer(gpc); // SECTION_ID_OUTPUTSTREAMS
996 1 gp_section_footer(gpc); // SECTION_ID_OUTPUTFILE
997 }
998
999 1 gp_section_footer(gpc); // SECTION_ID_OUTPUTFILES
1000
1001
1002 1 gp_section_header(gpc, NULL, SECTION_ID_STREAMLINKS);
1003
1004
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int n = 0; n < nb_ofiles; n++) {
1005 1 OutputFile *of = ofiles[n];
1006
1007
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < of->nb_streams; i++) {
1008 1 OutputStream *ost = of->streams[i];
1009
1010
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (ost->ist && !ost->filter) {
1011 ✗ sec_ctx.context_type = av_get_media_type_string(ost->type);
1012 ✗ gp_section_header(gpc, &sec_ctx, SECTION_ID_STREAMLINK);
1013 ✗ sec_ctx.context_type = NULL;
1014
1015 ✗ if (ost->enc) {
1016 ✗ print_fmt("dest_stream_id", "out__%d_%d", n, i);
1017 ✗ print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
1018 ✗ print_str("operation", "Transcode");
1019 } else {
1020 ✗ print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
1021 ✗ print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
1022 ✗ print_str("operation", "Stream Copy");
1023 }
1024
1025 ✗ print_str_opt("media_type", av_get_media_type_string(ost->type));
1026
1027 ✗ gp_section_footer(gpc); // SECTION_ID_STREAMLINK
1028 }
1029 }
1030 }
1031
1032 1 gp_section_footer(gpc); // SECTION_ID_STREAMLINKS
1033
1034 1 av_bprint_finalize(&buf, NULL);
1035 1 return 0;
1036 }
1037
1038
1039 2 static void uninit_graphprint(GraphPrintContext *gpc)
1040 {
1041
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (gpc->tfc)
1042 1 avtext_context_close(&gpc->tfc);
1043
1044
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (gpc->wctx)
1045 1 avtextwriter_context_close(&gpc->wctx);
1046
1047 // Finalize the print buffer if it was initialized
1048 2 av_bprint_finalize(&gpc->pbuf, NULL);
1049
1050 2 av_freep(&gpc);
1051 2 }
1052
1053 2 static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf, int record)
1054 {
1055 const AVTextFormatter *text_formatter;
1056 2 AVTextFormatContext *tfc = NULL;
1057 2 AVTextWriterContext *wctx = NULL;
1058 2 GraphPrintContext *gpc = NULL;
1059 int ret;
1060
1061 2 *pgpc = NULL;
1062
1063 2 av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
1064
1065
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const char *w_name = print_graphs_format ? print_graphs_format : "json";
1066
1067 2 text_formatter = avtext_get_formatter_by_name(w_name);
1068
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!text_formatter) {
1069 ✗ av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
1070 ✗ ret = AVERROR(EINVAL);
1071 ✗ goto fail;
1072 }
1073
1074
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (!record) {
1075 1 ret = avtextwriter_create_buffer(&wctx, target_buf);
1076
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0) {
1077 ✗ av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
1078 ✗ ret = AVERROR(EINVAL);
1079 ✗ goto fail;
1080 }
1081
1082 1 AVTextFormatOptions tf_options = { .show_optional_fields = -1 };
1083
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 const char *w_args = print_graphs_format ? strchr(print_graphs_format, '=') : NULL;
1084
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (w_args)
1085 ✗ ++w_args; // consume '='
1086 1 ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), tf_options, NULL);
1087
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0) {
1088 ✗ goto fail;
1089 }
1090 }
1091
1092 2 gpc = av_mallocz(sizeof(GraphPrintContext));
1093
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!gpc) {
1094 ✗ ret = AVERROR(ENOMEM);
1095 ✗ goto fail;
1096 }
1097
1098 2 gpc->wctx = wctx;
1099 2 gpc->tfc = tfc;
1100
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 gpc->rec = record ? target_buf : NULL;
1101 2 av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
1102
1103 2 gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
1104 2 gpc->is_diagram = !!(text_formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
1105
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (gpc->is_diagram) {
1106 ✗ gpc->show_value_unit = 1;
1107 ✗ gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
1108 ✗ gpc->skip_buffer_filters = 1;
1109 ✗ if (tfc) {
1110 ✗ tfc->opts.show_value_unit = 1;
1111 ✗ tfc->opts.show_optional_fields = -1;
1112 }
1113 ////} else {
1114 //// gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
1115 }
1116
1117
4/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
2 if (tfc && (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml"))) {
1118 ✗ gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
1119
1120 ✗ if (!strcmp(text_formatter->name, "mermaidhtml"))
1121 ✗ gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
1122
1123 ✗ av_diagram_init(tfc, &gpc->diagram_config);
1124 }
1125
1126 2 *pgpc = gpc;
1127
1128 2 return 0;
1129
1130 ✗ fail:
1131 ✗ if (tfc)
1132 ✗ avtext_context_close(&tfc);
1133 ✗ if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
1134 ✗ avtextwriter_context_close(&wctx);
1135 ✗ av_freep(&gpc);
1136
1137 ✗ return ret;
1138 }
1139
1140
1141 1 int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
1142 {
1143 av_assert2(fg);
1144
1145 1 GraphPrintContext *gpc = NULL;
1146 1 AVBPrint *target_buf = &fg->graph_print_buf;
1147 int ret;
1148
1149
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (target_buf->len)
1150 ✗ av_bprint_finalize(target_buf, NULL);
1151
1152 // Due to the threading model each graph needs to print itself from its own
1153 // thread. The actual printing happens short before cleanup in ffmpeg.c
1154 // where all graphs are assembled together, so the print calls are recorded
1155 // here and replayed there.
1156 1 ret = init_graphprint(&gpc, target_buf, 1);
1157
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret)
1158 ✗ return ret;
1159
1160 1 gp_section_header(gpc, NULL, SECTION_ID_FILTERGRAPH);
1161 1 print_filtergraph_single(gpc, fg, graph);
1162 1 gp_section_footer(gpc); // SECTION_ID_FILTERGRAPH
1163
1164 1 uninit_graphprint(gpc);
1165
1166 1 return 0;
1167 }
1168
1169 1 static void print_recorded_graph(const GraphPrintContext *gpc, AVBPrint *graph_buf)
1170 {
1171
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (graph_buf->len > 0) {
1172
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (!av_bprint_is_complete(graph_buf))
1173 ✗ av_log(NULL, AV_LOG_ERROR, "Incomplete record of a filter graph\n");
1174
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 else if (gp_replay(gpc, graph_buf) < 0)
1175 ✗ av_log(NULL, AV_LOG_ERROR, "Invalid record of a filter graph\n");
1176 1 av_bprint_finalize(graph_buf, NULL);
1177 }
1178 1 }
1179
1180 1 static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
1181 {
1182 1 GraphPrintContext *gpc = NULL;
1183 AVBPrint target_buf;
1184 int ret;
1185
1186 1 ret = init_graphprint(&gpc, &target_buf, 0);
1187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret)
1188 ✗ goto cleanup;
1189
1190 1 gp_section_header(gpc, NULL, SECTION_ID_ROOT);
1191 1 gp_section_header(gpc, NULL, SECTION_ID_FILTERGRAPHS);
1192
1193
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < nb_graphs; i++)
1194 1 print_recorded_graph(gpc, &graphs[i]->graph_print_buf);
1195
1196
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int n = 0; n < nb_ofiles; n++) {
1197 1 OutputFile *of = ofiles[n];
1198
1199
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < of->nb_streams; i++) {
1200 1 OutputStream *ost = of->streams[i];
1201
1202
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ost->fg_simple)
1203 ✗ print_recorded_graph(gpc, &ost->fg_simple->graph_print_buf);
1204 }
1205 }
1206
1207 1 gp_section_footer(gpc); // SECTION_ID_FILTERGRAPHS
1208
1209 1 print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
1210
1211 1 gp_section_footer(gpc); // SECTION_ID_ROOT
1212
1213
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (print_graphs_file) {
1214 1 AVIOContext *avio = NULL;
1215
1216
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!strcmp(print_graphs_file, "-")) {
1217 1 printf("%s", target_buf.str);
1218 } else {
1219 ✗ ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
1220 ✗ if (ret < 0) {
1221 ✗ av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
1222 ✗ goto cleanup;
1223 }
1224
1225 ✗ avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
1226
1227 ✗ if ((ret = avio_closep(&avio)) < 0)
1228 ✗ av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
1229 }
1230 }
1231
1232
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (print_graphs)
1233 ✗ av_log(NULL, AV_LOG_INFO, "%s %c", target_buf.str, '\n');
1234
1235 1 cleanup:
1236 // Properly clean up resources
1237
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (gpc)
1238 1 uninit_graphprint(gpc);
1239
1240 // Ensure the target buffer is properly finalized
1241 1 av_bprint_finalize(&target_buf, NULL);
1242
1243 1 return ret;
1244 }
1245
1246 1 int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
1247 {
1248 1 int ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
1249 1 ff_resman_uninit();
1250 1 return ret;
1251 }
1252