History

fftools/graphprint: Add execution graph printing

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

softworkz committed 1 year ago

Commit 45926bc1 parent 50fcc0c12 files · +1922 −2Browse files

Unified · Split

12 files
  1. doc/ffmpeg.texi +10 −0
  2. fftools/Makefile +19 −1
  3. fftools/ffmpeg.c +4 −0
  4. fftools/ffmpeg.h +3 −0
  5. fftools/ffmpeg_filter.c +5 −0
  6. fftools/ffmpeg_opt.c +13 −0
  7. fftools/graph/graphprint.c +1107 −0
  8. fftools/graph/graphprint.h +30 −0
  9. fftools/textformat/avtextformat.c +3 −1
  10. fftools/textformat/avtextformat.h +29 −0
  11. fftools/textformat/tf_mermaid.c +658 −0
  12. fftools/textformat/tf_mermaid.h +41 −0
doc/ffmpeg.texi+10−0
@@ -1394,6 +1394,16 @@ It is on by default, to explicitly disable it you need to specify @code{-nostats · 1393 unmodified lines
1394@item -stats_period @var{time} (@emph{global})1394@item -stats_period @var{time} (@emph{global})
1395Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.1395Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
1396 1396
1397@item -print_graphs (@emph{global})
1398Prints execution graph details to stderr in the format set via -print_graphs_format.
1399
1400@item -print_graphs_file @var{filename} (@emph{global})
1401Writes execution graph details to the specified file in the format set via -print_graphs_format.
1402
1403@item -print_graphs_format @var{format} (@emph{global})
1404Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
1405The default format is json.
1406
1397@item -progress @var{url} (@emph{global})1407@item -progress @var{url} (@emph{global})
1398Send program-friendly progress information to @var{url}.1408Send program-friendly progress information to @var{url}.
1399 1409
fftools/Makefile+19−1
@@ -9,6 +9,8 @@ AVBASENAMES = ffmpeg ffplay ffprobe · 8 unmodified lines
9ALLAVPROGS = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))9ALLAVPROGS = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
10ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))10ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
11 11
12include $(SRC_PATH)/fftools/resources/Makefile
13
12OBJS-ffmpeg += \14OBJS-ffmpeg += \
13 fftools/ffmpeg_dec.o \15 fftools/ffmpeg_dec.o \
14 fftools/ffmpeg_demux.o \16 fftools/ffmpeg_demux.o \
@@ -19,8 +21,21 @@ OBJS-ffmpeg += \ · 4 unmodified lines
19 fftools/ffmpeg_mux_init.o \21 fftools/ffmpeg_mux_init.o \
20 fftools/ffmpeg_opt.o \22 fftools/ffmpeg_opt.o \
21 fftools/ffmpeg_sched.o \23 fftools/ffmpeg_sched.o \
24 fftools/graph/graphprint.o \
22 fftools/sync_queue.o \25 fftools/sync_queue.o \
23 fftools/thread_queue.o \26 fftools/thread_queue.o \
27 fftools/textformat/avtextformat.o \
28 fftools/textformat/tf_compact.o \
29 fftools/textformat/tf_default.o \
30 fftools/textformat/tf_flat.o \
31 fftools/textformat/tf_ini.o \
32 fftools/textformat/tf_json.o \
33 fftools/textformat/tf_mermaid.o \
34 fftools/textformat/tf_xml.o \
35 fftools/textformat/tw_avio.o \
36 fftools/textformat/tw_buffer.o \
37 fftools/textformat/tw_stdout.o \
38 $(OBJS-resman) \
24 39
25OBJS-ffprobe += \40OBJS-ffprobe += \
26 fftools/textformat/avtextformat.o \41 fftools/textformat/avtextformat.o \
@@ -29,10 +44,12 @@ OBJS-ffprobe += \ · 2 unmodified lines
29 fftools/textformat/tf_flat.o \44 fftools/textformat/tf_flat.o \
30 fftools/textformat/tf_ini.o \45 fftools/textformat/tf_ini.o \
31 fftools/textformat/tf_json.o \46 fftools/textformat/tf_json.o \
47 fftools/textformat/tf_mermaid.o \
32 fftools/textformat/tf_xml.o \48 fftools/textformat/tf_xml.o \
33 fftools/textformat/tw_avio.o \49 fftools/textformat/tw_avio.o \
34 fftools/textformat/tw_buffer.o \50 fftools/textformat/tw_buffer.o \
35 fftools/textformat/tw_stdout.o \51 fftools/textformat/tw_stdout.o \
52 $(OBJS-resman) \
36 53
37OBJS-ffplay += fftools/ffplay_renderer.o54OBJS-ffplay += fftools/ffplay_renderer.o
38 55
@@ -42,7 +59,7 @@ ifdef HAVE_GNU_WINDRES · 3 unmodified lines
42OBJS-$(1) += fftools/fftoolsres.o59OBJS-$(1) += fftools/fftoolsres.o
43endif60endif
44$(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))61$(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
45$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources62$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources fftools/graph
46$$(OBJS-$(1)): CFLAGS += $(CFLAGS-$(1))63$$(OBJS-$(1)): CFLAGS += $(CFLAGS-$(1))
47$(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))64$(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
48$(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))65$(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
@@ -57,6 +74,7 @@ fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools · 8 unmodified lines
57OUTDIRS += fftools74OUTDIRS += fftools
58OUTDIRS += fftools/textformat75OUTDIRS += fftools/textformat
59OUTDIRS += fftools/resources76OUTDIRS += fftools/resources
77OUTDIRS += fftools/graph
60 78
61ifdef AVPROGS79ifdef AVPROGS
62install: install-progs install-data80install: install-progs install-data
fftools/ffmpeg.c+4−0
@@ -81,6 +81,7 @@ · 80 unmodified lines
81#include "ffmpeg.h"81#include "ffmpeg.h"
82#include "ffmpeg_sched.h"82#include "ffmpeg_sched.h"
83#include "ffmpeg_utils.h"83#include "ffmpeg_utils.h"
84#include "graph/graphprint.h"
84 85
85const char program_name[] = "ffmpeg";86const char program_name[] = "ffmpeg";
86const int program_birth_year = 2000;87const int program_birth_year = 2000;
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL }; · 221 unmodified lines
308 309
309static void ffmpeg_cleanup(int ret)310static void ffmpeg_cleanup(int ret)
310{311{
312 if (print_graphs || print_graphs_file)
313 print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
314
311 if (do_benchmark) {315 if (do_benchmark) {
312 int64_t maxrss = getmaxrss() / 1024;316 int64_t maxrss = getmaxrss() / 1024;
313 av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);317 av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
fftools/ffmpeg.h+3−0
@@ -717,6 +717,9 @@ extern float max_error_rate; · 716 unmodified lines
717extern char *filter_nbthreads;717extern char *filter_nbthreads;
718extern int filter_complex_nbthreads;718extern int filter_complex_nbthreads;
719extern int vstats_version;719extern int vstats_version;
720extern int print_graphs;
721extern char *print_graphs_file;
722extern char *print_graphs_format;
720extern int auto_conversion_filters;723extern int auto_conversion_filters;
721 724
722extern const AVIOInterruptCB int_cb;725extern const AVIOInterruptCB int_cb;
fftools/ffmpeg_filter.c+5−0
@@ -22,6 +22,7 @@ · 21 unmodified lines
22 22
23#include "ffmpeg.h"23#include "ffmpeg.h"
24#include "ffmpeg_filter.h"24#include "ffmpeg_filter.h"
25#include "graph/graphprint.h"
25 26
26#include "libavfilter/avfilter.h"27#include "libavfilter/avfilter.h"
27#include "libavfilter/buffersink.h"28#include "libavfilter/buffersink.h"
@@ -2983,6 +2984,10 @@ read_frames: · 2955 unmodified lines
2983 }2984 }
2984 2985
2985finish:2986finish:
2987
2988 if (print_graphs || print_graphs_file)
2989 print_filtergraph(fg, fgt.graph);
2990
2986 // EOF is normal termination2991 // EOF is normal termination
2987 if (ret == AVERROR_EOF)2992 if (ret == AVERROR_EOF)
2988 ret = 0;2993 ret = 0;
fftools/ffmpeg_opt.c+13−0
@@ -47,6 +47,7 @@ · 46 unmodified lines
47#include "libavutil/opt.h"47#include "libavutil/opt.h"
48#include "libavutil/parseutils.h"48#include "libavutil/parseutils.h"
49#include "libavutil/stereo3d.h"49#include "libavutil/stereo3d.h"
50#include "graph/graphprint.h"
50 51
51HWDevice *filter_hw_device;52HWDevice *filter_hw_device;
52 53
@@ -75,6 +76,9 @@ float max_error_rate = 2.0/3; · 22 unmodified lines
75char *filter_nbthreads;76char *filter_nbthreads;
76int filter_complex_nbthreads = 0;77int filter_complex_nbthreads = 0;
77int vstats_version = 2;78int vstats_version = 2;
79int print_graphs = 0;
80char *print_graphs_file = NULL;
81char *print_graphs_format = NULL;
78int auto_conversion_filters = 1;82int auto_conversion_filters = 1;
79int64_t stats_period = 500000;83int64_t stats_period = 500000;
80 84
@@ -1735,6 +1739,15 @@ const OptionDef options[] = { · 1654 unmodified lines
1735 { .func_arg = opt_filter_complex_script },1739 { .func_arg = opt_filter_complex_script },
1736 "deprecated, use -/filter_complex instead", "filename" },1740 "deprecated, use -/filter_complex instead", "filename" },
1737#endif1741#endif
1742 { "print_graphs", OPT_TYPE_BOOL, 0,
1743 { &print_graphs },
1744 "print execution graph data to stderr" },
1745 { "print_graphs_file", OPT_TYPE_STRING, 0,
1746 { &print_graphs_file },
1747 "write execution graph data to the specified file", "filename" },
1748 { "print_graphs_format", OPT_TYPE_STRING, 0,
1749 { &print_graphs_format },
1750 "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
1738 { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,1751 { "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
1739 { &auto_conversion_filters },1752 { &auto_conversion_filters },
1740 "enable automatic conversion filters globally" },1753 "enable automatic conversion filters globally" },
fftools/graph/graphprint.c+1107−0
@@ -0,0 +1,1107 @@
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_filter.h"
32#include "fftools/ffmpeg_mux.h"
33
34#include "libavutil/avassert.h"
35#include "libavutil/avstring.h"
36#include "libavutil/pixdesc.h"
37#include "libavutil/dict.h"
38#include "libavutil/common.h"
39#include "libavfilter/avfilter.h"
40#include "libavutil/buffer.h"
41#include "libavutil/hwcontext.h"
42#include "fftools/textformat/avtextformat.h"
43#include "fftools/textformat/tf_mermaid.h"
44#include "fftools/resources/resman.h"
45
46typedef enum {
47 SECTION_ID_ROOT,
48 SECTION_ID_FILTERGRAPHS,
49 SECTION_ID_FILTERGRAPH,
50 SECTION_ID_GRAPH_INPUTS,
51 SECTION_ID_GRAPH_INPUT,
52 SECTION_ID_GRAPH_OUTPUTS,
53 SECTION_ID_GRAPH_OUTPUT,
54 SECTION_ID_FILTERS,
55 SECTION_ID_FILTER,
56 SECTION_ID_FILTER_INPUTS,
57 SECTION_ID_FILTER_INPUT,
58 SECTION_ID_FILTER_OUTPUTS,
59 SECTION_ID_FILTER_OUTPUT,
60 SECTION_ID_HWFRAMESCONTEXT,
61 SECTION_ID_INPUTFILES,
62 SECTION_ID_INPUTFILE,
63 SECTION_ID_INPUTSTREAMS,
64 SECTION_ID_INPUTSTREAM,
65 SECTION_ID_OUTPUTFILES,
66 SECTION_ID_OUTPUTFILE,
67 SECTION_ID_OUTPUTSTREAMS,
68 SECTION_ID_OUTPUTSTREAM,
69 SECTION_ID_STREAMLINKS,
70 SECTION_ID_STREAMLINK,
71 SECTION_ID_DECODERS,
72 SECTION_ID_DECODER,
73 SECTION_ID_ENCODERS,
74 SECTION_ID_ENCODER,
75} SectionID;
76
77static struct AVTextFormatSection sections[] = {
78 [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 } },
79
80 [SECTION_ID_FILTERGRAPHS] = { SECTION_ID_FILTERGRAPHS, "graphs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTERGRAPH, -1 } },
81 [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" },
82
83 [SECTION_ID_GRAPH_INPUTS] = { SECTION_ID_GRAPH_INPUTS, "graph_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_INPUT, -1 }, .id_key = "id" },
84 [SECTION_ID_GRAPH_INPUT] = { SECTION_ID_GRAPH_INPUT, "graph_input", 0, { -1 }, .id_key = "filter_id" },
85
86 [SECTION_ID_GRAPH_OUTPUTS] = { SECTION_ID_GRAPH_OUTPUTS, "graph_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_GRAPH_OUTPUT, -1 }, .id_key = "id" },
87 [SECTION_ID_GRAPH_OUTPUT] = { SECTION_ID_GRAPH_OUTPUT, "graph_output", 0, { -1 }, .id_key = "filter_id" },
88
89 [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" },
90 [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" },
91
92 [SECTION_ID_FILTER_INPUTS] = { SECTION_ID_FILTER_INPUTS, "filter_inputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_INPUT, -1 } },
93 [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" },
94
95 [SECTION_ID_FILTER_OUTPUTS] = { SECTION_ID_FILTER_OUTPUTS, "filter_outputs", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_FILTER_OUTPUT, -1 } },
96 [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" },
97
98 [SECTION_ID_HWFRAMESCONTEXT] = { SECTION_ID_HWFRAMESCONTEXT, "hw_frames_context", 0, { -1 }, },
99
100 [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" },
101 [SECTION_ID_INPUTFILE] = { SECTION_ID_INPUTFILE, "inputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_INPUTSTREAMS, -1 }, .id_key = "id" },
102
103 [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" },
104 [SECTION_ID_INPUTSTREAM] = { SECTION_ID_INPUTSTREAM, "inputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id" },
105
106 [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" },
107 [SECTION_ID_OUTPUTFILE] = { SECTION_ID_OUTPUTFILE, "outputfile", AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_OUTPUTSTREAMS, -1 }, .id_key = "id" },
108
109 [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" },
110 [SECTION_ID_OUTPUTSTREAM] = { SECTION_ID_OUTPUTSTREAM, "outputstream", AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS, { -1 }, .id_key = "id", },
111
112 [SECTION_ID_STREAMLINKS] = { SECTION_ID_STREAMLINKS, "streamlinks", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAMLINK, -1 } },
113 [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" },
114
115 [SECTION_ID_DECODERS] = { SECTION_ID_DECODERS, "decoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_DECODER, -1 } },
116 [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" },
117
118 [SECTION_ID_ENCODERS] = { SECTION_ID_ENCODERS, "encoders", AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH, { SECTION_ID_ENCODER, -1 } },
119 [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" },
120};
121
122typedef struct GraphPrintContext {
123 AVTextFormatContext *tfc;
124 AVTextWriterContext *wctx;
125 AVDiagramConfig diagram_config;
126
127 int id_prefix_num;
128 int is_diagram;
129 int opt_flags;
130 int skip_buffer_filters;
131 AVBPrint pbuf;
132
133} GraphPrintContext;
134
135/* Text Format API Shortcuts */
136#define print_id(k, v) print_sanizied_id(gpc, k, v, 0)
137#define print_id_noprefix(k, v) print_sanizied_id(gpc, k, v, 1)
138#define print_int(k, v) avtext_print_integer(tfc, k, v, 0)
139#define print_int_opt(k, v) avtext_print_integer(tfc, k, v, gpc->opt_flags)
140#define print_q(k, v, s) avtext_print_rational(tfc, k, v, s)
141#define print_str(k, v) avtext_print_string(tfc, k, v, 0)
142#define print_str_opt(k, v) avtext_print_string(tfc, k, v, gpc->opt_flags)
143#define print_val(k, v, u) avtext_print_unit_int(tfc, k, v, u)
144
145#define print_fmt(k, f, ...) do { \
146 av_bprint_clear(&gpc->pbuf); \
147 av_bprintf(&gpc->pbuf, f, __VA_ARGS__); \
148 avtext_print_string(tfc, k, gpc->pbuf.str, 0); \
149} while (0)
150
151#define print_fmt_opt(k, f, ...) do { \
152 av_bprint_clear(&gpc->pbuf); \
153 av_bprintf(&gpc->pbuf, f, __VA_ARGS__); \
154 avtext_print_string(tfc, k, gpc->pbuf.str, gpc->opt_flags); \
155} while (0)
156
157
158static atomic_int prefix_num = 0;
159
160static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
161{
162 unsigned i;
163 for (i = 0; src[i] && i < dst_size - 1; i++)
164 dst[i] = (char)av_toupper(src[i]);
165 dst[i] = 0;
166 return dst;
167}
168
169static char *get_extension(const char *url)
170{
171 const char *dot = NULL;
172 const char *sep = NULL;
173 const char *end;
174
175 if (!url)
176 return NULL;
177
178 /* Stop at the first query ('?') or fragment ('#') delimiter so they
179 * are not considered part of the path. */
180 end = strpbrk(url, "?#");
181 if (!end)
182 end = url + strlen(url);
183
184 /* Scan the path component only. */
185 for (const char *p = url; p < end; p++) {
186 if (*p == '.')
187 dot = p;
188 else if (*p == '/' || *p == '\\')
189 sep = p;
190 }
191
192 /* Validate that we have a proper extension. */
193 if (dot && dot != url && (!sep || dot > sep + 1) && (dot + 1) < end) {
194 /* Use FFmpeg helper to duplicate the substring. */
195 return av_strndup(dot + 1, end - (dot + 1));
196 }
197
198 return NULL;
199}
200
201static void print_hwdevicecontext(const GraphPrintContext *gpc, const AVHWDeviceContext *hw_device_context)
202{
203 AVTextFormatContext *tfc = gpc->tfc;
204
205 if (!hw_device_context)
206 return;
207
208 print_int_opt("has_hw_device_context", 1);
209 print_str_opt("hw_device_type", av_hwdevice_get_type_name(hw_device_context->type));
210}
211
212static void print_hwframescontext(const GraphPrintContext *gpc, const AVHWFramesContext *hw_frames_context)
213{
214 AVTextFormatContext *tfc = gpc->tfc;
215 const AVPixFmtDescriptor *pix_desc_hw;
216 const AVPixFmtDescriptor *pix_desc_sw;
217
218 if (!hw_frames_context || !hw_frames_context->device_ctx)
219 return;
220
221 avtext_print_section_header(tfc, NULL, SECTION_ID_HWFRAMESCONTEXT);
222
223 print_int_opt("has_hw_frames_context", 1);
224 print_str("hw_device_type", av_hwdevice_get_type_name(hw_frames_context->device_ctx->type));
225
226 pix_desc_hw = av_pix_fmt_desc_get(hw_frames_context->format);
227 if (pix_desc_hw) {
228 print_str("hw_pixel_format", pix_desc_hw->name);
229 if (pix_desc_hw->alias)
230 print_str_opt("hw_pixel_format_alias", pix_desc_hw->alias);
231 }
232
233 pix_desc_sw = av_pix_fmt_desc_get(hw_frames_context->sw_format);
234 if (pix_desc_sw) {
235 print_str("sw_pixel_format", pix_desc_sw->name);
236 if (pix_desc_sw->alias)
237 print_str_opt("sw_pixel_format_alias", pix_desc_sw->alias);
238 }
239
240 print_int_opt("width", hw_frames_context->width);
241 print_int_opt("height", hw_frames_context->height);
242 print_int_opt("initial_pool_size", hw_frames_context->initial_pool_size);
243
244 avtext_print_section_footer(tfc); // SECTION_ID_HWFRAMESCONTEXT
245}
246
247static void print_link(GraphPrintContext *gpc, AVFilterLink *link)
248{
249 AVTextFormatContext *tfc = gpc->tfc;
250 AVBufferRef *hw_frames_ctx;
251 char layout_string[64];
252
253 if (!link)
254 return;
255
256 hw_frames_ctx = avfilter_link_get_hw_frames_ctx(link);
257
258 print_str_opt("media_type", av_get_media_type_string(link->type));
259
260 switch (link->type) {
261 case AVMEDIA_TYPE_VIDEO:
262
263 if (hw_frames_ctx && hw_frames_ctx->data) {
264 AVHWFramesContext * hwfctx = (AVHWFramesContext *)hw_frames_ctx->data;
265 const AVPixFmtDescriptor *pix_desc_hw = av_pix_fmt_desc_get(hwfctx->format);
266 const AVPixFmtDescriptor *pix_desc_sw = av_pix_fmt_desc_get(hwfctx->sw_format);
267 if (pix_desc_hw && pix_desc_sw)
268 print_fmt("format", "%s | %s", pix_desc_hw->name, pix_desc_sw->name);
269 } else {
270 print_str("format", av_x_if_null(av_get_pix_fmt_name(link->format), "?"));
271 }
272
273 if (link->w && link->h) {
274 if (tfc->show_value_unit) {
275 print_fmt("size", "%dx%d", link->w, link->h);
276 } else {
277 print_int("width", link->w);
278 print_int("height", link->h);
279 }
280 }
281
282 print_q("sar", link->sample_aspect_ratio, ':');
283
284 if (link->color_range != AVCOL_RANGE_UNSPECIFIED)
285 print_str_opt("color_range", av_color_range_name(link->color_range));
286
287 if (link->colorspace != AVCOL_SPC_UNSPECIFIED)
288 print_str("color_space", av_color_space_name(link->colorspace));
289 break;
290
291 case AVMEDIA_TYPE_SUBTITLE:
292 ////print_str("format", av_x_if_null(av_get_subtitle_fmt_name(link->format), "?"));
293
294 if (link->w && link->h) {
295 if (tfc->show_value_unit) {
296 print_fmt("size", "%dx%d", link->w, link->h);
297 } else {
298 print_int("width", link->w);
299 print_int("height", link->h);
300 }
301 }
302
303 break;
304
305 case AVMEDIA_TYPE_AUDIO:
306 av_channel_layout_describe(&link->ch_layout, layout_string, sizeof(layout_string));
307 print_str("channel_layout", layout_string);
308 print_val("channels", link->ch_layout.nb_channels, "ch");
309 if (tfc->show_value_unit)
310 print_fmt("sample_rate", "%d.1 kHz", link->sample_rate / 1000);
311 else
312 print_val("sample_rate", link->sample_rate, "Hz");
313
314 break;
315 }
316
317 print_fmt_opt("sample_rate", "%d/%d", link->time_base.num, link->time_base.den);
318
319 if (hw_frames_ctx && hw_frames_ctx->data)
320 print_hwframescontext(gpc, (AVHWFramesContext *)hw_frames_ctx->data);
321}
322
323static char sanitize_char(const char c)
324{
325 if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
326 return c;
327 return '_';
328}
329
330static void print_sanizied_id(const GraphPrintContext *gpc, const char *key, const char *id_str, int skip_prefix)
331{
332 AVTextFormatContext *tfc = gpc->tfc;
333 AVBPrint buf;
334
335 if (!key || !id_str)
336 return;
337
338 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
339
340 if (!skip_prefix)
341 av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
342
343 // sanizize section id
344 for (const char *p = id_str; *p; p++)
345 av_bprint_chars(&buf, sanitize_char(*p), 1);
346
347 print_str(key, buf.str);
348
349 av_bprint_finalize(&buf, NULL);
350}
351
352static void print_section_header_id(const GraphPrintContext *gpc, int section_id, const char *id_str, int skip_prefix)
353{
354 AVTextFormatContext *tfc = gpc->tfc;
355 AVTextFormatSectionContext sec_ctx = { 0 };
356 AVBPrint buf;
357
358 if (!id_str)
359 return;
360
361 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
362
363 if (!skip_prefix)
364 av_bprintf(&buf, "G%d_", gpc->id_prefix_num);
365
366 // sanizize section id
367 for (const char *p = id_str; *p; p++)
368 av_bprint_chars(&buf, sanitize_char(*p), 1);
369
370 sec_ctx.context_id = buf.str;
371
372 avtext_print_section_header(tfc, &sec_ctx, section_id);
373
374 av_bprint_finalize(&buf, NULL);
375}
376
377static const char *get_filterpad_name(const AVFilterPad *pad)
378{
379 return pad ? avfilter_pad_get_name(pad, 0) : "pad";
380}
381
382static void print_filter(GraphPrintContext *gpc, const AVFilterContext *filter, AVDictionary *input_map, AVDictionary *output_map)
383{
384 AVTextFormatContext *tfc = gpc->tfc;
385 AVTextFormatSectionContext sec_ctx = { 0 };
386
387 print_section_header_id(gpc, SECTION_ID_FILTER, filter->name, 0);
388
389 ////print_id("filter_id", filter->name);
390
391 if (filter->filter) {
392 print_str("filter_name", filter->filter->name);
393 print_str_opt("description", filter->filter->description);
394 print_int_opt("nb_inputs", filter->nb_inputs);
395 print_int_opt("nb_outputs", filter->nb_outputs);
396 }
397
398 if (filter->hw_device_ctx) {
399 AVHWDeviceContext *device_context = (AVHWDeviceContext *)filter->hw_device_ctx->data;
400 print_hwdevicecontext(gpc, device_context);
401 if (filter->extra_hw_frames > 0)
402 print_int("extra_hw_frames", filter->extra_hw_frames);
403 }
404
405 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_INPUTS);
406
407 for (unsigned i = 0; i < filter->nb_inputs; i++) {
408 AVDictionaryEntry *dic_entry;
409 AVFilterLink *link = filter->inputs[i];
410
411 sec_ctx.context_type = av_get_media_type_string(link->type);
412 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_INPUT);
413 sec_ctx.context_type = NULL;
414
415 print_int_opt("input_index", i);
416 print_str_opt("pad_name", get_filterpad_name(link->dstpad));;
417
418 dic_entry = av_dict_get(input_map, link->src->name, NULL, 0);
419 if (dic_entry) {
420 char buf[256];
421 (void)snprintf(buf, sizeof(buf), "in_%s", dic_entry->value);
422 print_id_noprefix("source_filter_id", buf);
423 } else {
424 print_id("source_filter_id", link->src->name);
425 }
426
427 print_str_opt("source_pad_name", get_filterpad_name(link->srcpad));
428 print_id("filter_id", filter->name);
429
430 print_link(gpc, link);
431
432 avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUT
433 }
434
435 avtext_print_section_footer(tfc); // SECTION_ID_FILTER_INPUTS
436
437 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTER_OUTPUTS);
438
439 for (unsigned i = 0; i < filter->nb_outputs; i++) {
440 AVDictionaryEntry *dic_entry;
441 AVFilterLink *link = filter->outputs[i];
442 char buf[256];
443
444 sec_ctx.context_type = av_get_media_type_string(link->type);
445 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTER_OUTPUT);
446 sec_ctx.context_type = NULL;
447
448 dic_entry = av_dict_get(output_map, link->dst->name, NULL, 0);
449 if (dic_entry) {
450 (void)snprintf(buf, sizeof(buf), "out_%s", dic_entry->value);
451 print_id_noprefix("dest_filter_id", buf);
452 } else {
453 print_id("dest_filter_id", link->dst->name);
454 }
455
456 print_int_opt("output_index", i);
457 print_str_opt("pad_name", get_filterpad_name(link->srcpad));
458 ////print_id("dest_filter_id", link->dst->name);
459 print_str_opt("dest_pad_name", get_filterpad_name(link->dstpad));
460 print_id("filter_id", filter->name);
461
462 print_link(gpc, link);
463
464 avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUT
465 }
466
467 avtext_print_section_footer(tfc); // SECTION_ID_FILTER_OUTPUTS
468
469 avtext_print_section_footer(tfc); // SECTION_ID_FILTER
470}
471
472static void init_sections(void)
473{
474 for (unsigned i = 0; i < FF_ARRAY_ELEMS(sections); i++)
475 sections[i].show_all_entries = 1;
476}
477
478static void print_filtergraph_single(GraphPrintContext *gpc, FilterGraph *fg, AVFilterGraph *graph)
479{
480 AVTextFormatContext *tfc = gpc->tfc;
481 FilterGraphPriv *fgp = fgp_from_fg(fg);
482 AVDictionary *input_map = NULL;
483 AVDictionary *output_map = NULL;
484
485 print_int("graph_index", fg->index);
486 print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
487 print_fmt("id", "Graph_%d_%d", gpc->id_prefix_num, fg->index);
488 print_str("description", fgp->graph_desc);
489
490 print_section_header_id(gpc, SECTION_ID_GRAPH_INPUTS, "Input_File", 0);
491
492 for (int i = 0; i < fg->nb_inputs; i++) {
493 InputFilterPriv *ifilter = ifp_from_ifilter(fg->inputs[i]);
494 enum AVMediaType media_type = ifilter->type;
495
496 avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_INPUT);
497
498 print_int("input_index", ifilter->index);
499
500 if (ifilter->linklabel)
501 print_str("link_label", (const char*)ifilter->linklabel);
502
503 if (ifilter->filter) {
504 print_id("filter_id", ifilter->filter->name);
505 print_str("filter_name", ifilter->filter->filter->name);
506 }
507
508 if (ifilter->linklabel && ifilter->filter)
509 av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->linklabel, 0);
510 else if (ifilter->opts.name && ifilter->filter)
511 av_dict_set(&input_map, ifilter->filter->name, (const char *)ifilter->opts.name, 0);
512
513 print_str("media_type", av_get_media_type_string(media_type));
514
515 avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUT
516 }
517
518 avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_INPUTS
519
520 print_section_header_id(gpc, SECTION_ID_GRAPH_OUTPUTS, "Output_File", 0);
521
522 for (int i = 0; i < fg->nb_outputs; i++) {
523 OutputFilterPriv *ofilter = ofp_from_ofilter(fg->outputs[i]);
524
525 avtext_print_section_header(tfc, NULL, SECTION_ID_GRAPH_OUTPUT);
526
527 print_int("output_index", ofilter->index);
528
529 print_str("name", ofilter->name);
530
531 if (fg->outputs[i]->linklabel)
532 print_str("link_label", (const char*)fg->outputs[i]->linklabel);
533
534 if (ofilter->filter) {
535 print_id("filter_id", ofilter->filter->name);
536 print_str("filter_name", ofilter->filter->filter->name);
537 }
538
539 if (ofilter->name && ofilter->filter)
540 av_dict_set(&output_map, ofilter->filter->name, ofilter->name, 0);
541
542
543 print_str("media_type", av_get_media_type_string(fg->outputs[i]->type));
544
545 avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUT
546 }
547
548 avtext_print_section_footer(tfc); // SECTION_ID_GRAPH_OUTPUTS
549
550 if (graph) {
551 AVTextFormatSectionContext sec_ctx = { 0 };
552
553 sec_ctx.context_id = av_asprintf("Graph_%d_%d", gpc->id_prefix_num, fg->index);
554
555 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_FILTERS);
556
557 if (gpc->is_diagram) {
558 print_fmt("name", "Graph %d.%d", gpc->id_prefix_num, fg->index);
559 print_str("description", fgp->graph_desc);
560 print_str("id", sec_ctx.context_id);
561 }
562
563 av_freep(&sec_ctx.context_id);
564
565 for (unsigned i = 0; i < graph->nb_filters; i++) {
566 AVFilterContext *filter = graph->filters[i];
567
568 if (gpc->skip_buffer_filters) {
569 if (av_dict_get(input_map, filter->name, NULL, 0))
570 continue;
571 if (av_dict_get(output_map, filter->name, NULL, 0))
572 continue;
573 }
574
575 sec_ctx.context_id = filter->name;
576
577 print_filter(gpc, filter, input_map, output_map);
578 }
579
580 avtext_print_section_footer(tfc); // SECTION_ID_FILTERS
581 }
582
583 // Clean up dictionaries
584 av_dict_free(&input_map);
585 av_dict_free(&output_map);
586}
587
588static int print_streams(GraphPrintContext *gpc, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
589{
590 AVTextFormatContext *tfc = gpc->tfc;
591 AVBPrint buf;
592 AVTextFormatSectionContext sec_ctx = { 0 };
593
594 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
595
596 print_section_header_id(gpc, SECTION_ID_INPUTFILES, "Inputs", 0);
597
598 for (int n = nb_ifiles - 1; n >= 0; n--) {
599 InputFile *ifi = ifiles[n];
600 AVFormatContext *fc = ifi->ctx;
601
602 sec_ctx.context_id = av_asprintf("Input_%d", n);
603 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTFILE);
604 av_freep(&sec_ctx.context_id);
605
606 print_fmt("index", "%d", ifi->index);
607
608 if (fc) {
609 print_str("demuxer_name", fc->iformat->name);
610 if (fc->url) {
611 char *extension = get_extension(fc->url);
612 if (extension) {
613 print_str("file_extension", extension);
614 av_freep(&extension);
615 }
616 print_str("url", fc->url);
617 }
618 }
619
620 sec_ctx.context_id = av_asprintf("InputStreams_%d", n);
621
622 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAMS);
623
624 av_freep(&sec_ctx.context_id);
625
626 for (int i = 0; i < ifi->nb_streams; i++) {
627 InputStream *ist = ifi->streams[i];
628 const AVCodecDescriptor *codec_desc;
629
630 if (!ist || !ist->par)
631 continue;
632
633 codec_desc = avcodec_descriptor_get(ist->par->codec_id);
634
635 sec_ctx.context_id = av_asprintf("r_in_%d_%d", n, i);
636
637 sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
638
639 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_INPUTSTREAM);
640 av_freep(&sec_ctx.context_id);
641 sec_ctx.context_type = NULL;
642
643 av_bprint_clear(&buf);
644
645 print_fmt("id", "r_in_%d_%d", n, i);
646
647 if (codec_desc && codec_desc->name) {
648 ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), codec_desc->long_name));
649 av_bprintf(&buf, "%s", codec_desc->long_name);
650 } else if (ist->dec) {
651 char char_buf[256];
652 av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
653 } else if (ist->par->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
654 av_bprintf(&buf, "%s", "Attachment");
655 } else if (ist->par->codec_type == AVMEDIA_TYPE_DATA) {
656 av_bprintf(&buf, "%s", "Data");
657 }
658
659 print_fmt("name", "%s", buf.str);
660 print_fmt("index", "%d", ist->index);
661
662 if (ist->dec)
663 print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
664
665 avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAM
666 }
667
668 avtext_print_section_footer(tfc); // SECTION_ID_INPUTSTREAMS
669 avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILE
670 }
671
672 avtext_print_section_footer(tfc); // SECTION_ID_INPUTFILES
673
674
675 print_section_header_id(gpc, SECTION_ID_DECODERS, "Decoders", 0);
676
677 for (int n = 0; n < nb_ifiles; n++) {
678 InputFile *ifi = ifiles[n];
679
680 for (int i = 0; i < ifi->nb_streams; i++) {
681 InputStream *ist = ifi->streams[i];
682
683 if (!ist->decoder)
684 continue;
685
686 sec_ctx.context_id = av_asprintf("in_%d_%d", n, i);
687 sec_ctx.context_type = av_get_media_type_string(ist->par->codec_type);
688 sec_ctx.context_flags = 2;
689
690 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_DECODER);
691 av_freep(&sec_ctx.context_id);
692 sec_ctx.context_type = NULL;
693 sec_ctx.context_flags = 0;
694
695 av_bprint_clear(&buf);
696
697 print_fmt("source_id", "r_in_%d_%d", n, i);
698 print_fmt("id", "in_%d_%d", n, i);
699
700 ////av_bprintf(&buf, "%s", upcase_string(char_buf, sizeof(char_buf), ist->dec->name));
701 print_fmt("name", "%s", ist->dec->name);
702
703 print_str_opt("media_type", av_get_media_type_string(ist->par->codec_type));
704
705 avtext_print_section_footer(tfc); // SECTION_ID_DECODER
706 }
707 }
708
709 avtext_print_section_footer(tfc); // SECTION_ID_DECODERS
710
711
712 print_section_header_id(gpc, SECTION_ID_ENCODERS, "Encoders", 0);
713
714 for (int n = 0; n < nb_ofiles; n++) {
715 OutputFile *of = ofiles[n];
716
717 for (int i = 0; i < of->nb_streams; i++) {
718 OutputStream *ost = of->streams[i];
719 ////const AVCodecDescriptor *codec_desc;
720
721 if (!ost || !ost->st || !ost->st->codecpar || !ost->enc)
722 continue;
723
724 ////codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
725
726 sec_ctx.context_id = av_asprintf("out__%d_%d", n, i);
727 sec_ctx.context_type = av_get_media_type_string(ost->type);
728 sec_ctx.context_flags = 2;
729
730 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_ENCODER);
731 av_freep(&sec_ctx.context_id);
732 sec_ctx.context_type = NULL;
733 sec_ctx.context_flags = 0;
734
735 av_bprint_clear(&buf);
736
737 print_fmt("id", "out__%d_%d", n, i);
738 print_fmt("dest_id", "r_out__%d_%d", n, i);
739
740 print_fmt("name", "%s", ost->enc->enc_ctx->av_class->item_name(ost->enc->enc_ctx));
741
742 print_str_opt("media_type", av_get_media_type_string(ost->type));
743
744 avtext_print_section_footer(tfc); // SECTION_ID_ENCODER
745 }
746 }
747
748 avtext_print_section_footer(tfc); // SECTION_ID_ENCODERS
749
750
751 print_section_header_id(gpc, SECTION_ID_OUTPUTFILES, "Outputs", 0);
752
753 for (int n = nb_ofiles - 1; n >= 0; n--) {
754 OutputFile *of = ofiles[n];
755 Muxer *muxer = (Muxer *)of;
756
757 if (!muxer->fc)
758 continue;
759
760 sec_ctx.context_id = av_asprintf("Output_%d", n);
761
762 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTFILE);
763
764 av_freep(&sec_ctx.context_id);
765
766 ////print_str_opt("index", av_get_media_type_string(of->index));
767 print_fmt("index", "%d", of->index);
768 ////print_str("url", of->url);
769 print_str("muxer_name", muxer->fc->oformat->name);
770 if (of->url) {
771 char *extension = get_extension(of->url);
772 if (extension) {
773 print_str("file_extension", extension);
774 av_freep(&extension);
775 }
776 print_str("url", of->url);
777 }
778
779 sec_ctx.context_id = av_asprintf("OutputStreams_%d", n);
780
781 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAMS);
782
783 for (int i = 0; i < of->nb_streams; i++) {
784 OutputStream *ost = of->streams[i];
785 const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ost->st->codecpar->codec_id);
786
787 sec_ctx.context_id = av_asprintf("r_out__%d_%d", n, i);
788 sec_ctx.context_type = av_get_media_type_string(ost->type);
789 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_OUTPUTSTREAM);
790 av_freep(&sec_ctx.context_id);
791 sec_ctx.context_type = NULL;
792
793 av_bprint_clear(&buf);
794
795 print_fmt("id", "r_out__%d_%d", n, i);
796
797 if (codec_desc && codec_desc->name) {
798 av_bprintf(&buf, "%s", codec_desc->long_name);
799 } else {
800 av_bprintf(&buf, "%s", "unknown");
801 }
802
803 print_fmt("name", "%s", buf.str);
804 print_fmt("index", "%d", ost->index);
805
806 print_str_opt("media_type", av_get_media_type_string(ost->type));
807
808 avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAM
809 }
810
811 avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTSTREAMS
812 avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILE
813 }
814
815 avtext_print_section_footer(tfc); // SECTION_ID_OUTPUTFILES
816
817
818 avtext_print_section_header(tfc, NULL, SECTION_ID_STREAMLINKS);
819
820 for (int n = 0; n < nb_ofiles; n++) {
821 OutputFile *of = ofiles[n];
822
823 for (int i = 0; i < of->nb_streams; i++) {
824 OutputStream *ost = of->streams[i];
825
826 if (ost->ist && !ost->filter) {
827 sec_ctx.context_type = av_get_media_type_string(ost->type);
828 avtext_print_section_header(tfc, &sec_ctx, SECTION_ID_STREAMLINK);
829 sec_ctx.context_type = NULL;
830
831 if (ost->enc) {
832 print_fmt("dest_stream_id", "out__%d_%d", n, i);
833 print_fmt("source_stream_id", "in_%d_%d", ost->ist->file->index, ost->ist->index);
834 print_str("operation", "Transcode");
835 } else {
836 print_fmt("dest_stream_id", "r_out__%d_%d", n, i);
837 print_fmt("source_stream_id", "r_in_%d_%d", ost->ist->file->index, ost->ist->index);
838 print_str("operation", "Stream Copy");
839 }
840
841 print_str_opt("media_type", av_get_media_type_string(ost->type));
842
843 avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINK
844 }
845 }
846 }
847
848 avtext_print_section_footer(tfc); // SECTION_ID_STREAMLINKS
849
850 av_bprint_finalize(&buf, NULL);
851 return 0;
852}
853
854
855static void uninit_graphprint(GraphPrintContext *gpc)
856{
857 if (gpc->tfc)
858 avtext_context_close(&gpc->tfc);
859
860 if (gpc->wctx)
861 avtextwriter_context_close(&gpc->wctx);
862
863 // Finalize the print buffer if it was initialized
864 av_bprint_finalize(&gpc->pbuf, NULL);
865}
866
867static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
868{
869 const AVTextFormatter *text_formatter;
870 AVTextFormatContext *tfc = NULL;
871 AVTextWriterContext *wctx = NULL;
872 GraphPrintContext *gpc = NULL;
873 char *w_args = NULL;
874 char *w_name;
875 int ret;
876
877 init_sections();
878 *pgpc = NULL;
879
880 av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
881
882 if (!print_graphs_format)
883 print_graphs_format = av_strdup("json");
884 if (!print_graphs_format) {
885 ret = AVERROR(ENOMEM);
886 goto fail;
887 }
888
889 w_name = av_strtok(print_graphs_format, "=", &w_args);
890 if (!w_name) {
891 av_log(NULL, AV_LOG_ERROR, "No name specified for the filter graph output format\n");
892 ret = AVERROR(EINVAL);
893 goto fail;
894 }
895
896 text_formatter = avtext_get_formatter_by_name(w_name);
897 if (!text_formatter) {
898 av_log(NULL, AV_LOG_ERROR, "Unknown filter graph output format with name '%s'\n", w_name);
899 ret = AVERROR(EINVAL);
900 goto fail;
901 }
902
903 ret = avtextwriter_create_buffer(&wctx, target_buf);
904 if (ret < 0) {
905 av_log(NULL, AV_LOG_ERROR, "avtextwriter_create_buffer failed. Error code %d\n", ret);
906 ret = AVERROR(EINVAL);
907 goto fail;
908 }
909
910 AVTextFormatOptions tf_options = { .show_optional_fields = -1 };
911 ret = avtext_context_open(&tfc, text_formatter, wctx, w_args, sections, FF_ARRAY_ELEMS(sections), tf_options, NULL);
912 if (ret < 0) {
913 goto fail;
914 }
915
916 gpc = av_mallocz(sizeof(GraphPrintContext));
917 if (!gpc) {
918 ret = AVERROR(ENOMEM);
919 goto fail;
920 }
921
922 gpc->wctx = wctx;
923 gpc->tfc = tfc;
924 av_bprint_init(&gpc->pbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
925
926 gpc->id_prefix_num = atomic_fetch_add(&prefix_num, 1);
927 gpc->is_diagram = !!(tfc->formatter->flags & AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER);
928 if (gpc->is_diagram) {
929 tfc->show_value_unit = 1;
930 tfc->show_optional_fields = -1;
931 gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
932 gpc->skip_buffer_filters = 1;
933 ////} else {
934 //// gpc->opt_flags = AV_TEXTFORMAT_PRINT_STRING_OPTIONAL;
935 }
936
937 if (!strcmp(text_formatter->name, "mermaid") || !strcmp(text_formatter->name, "mermaidhtml")) {
938 gpc->diagram_config.diagram_css = ff_resman_get_string(FF_RESOURCE_GRAPH_CSS);
939
940 if (!strcmp(text_formatter->name, "mermaidhtml"))
941 gpc->diagram_config.html_template = ff_resman_get_string(FF_RESOURCE_GRAPH_HTML);
942
943 av_diagram_init(tfc, &gpc->diagram_config);
944 }
945
946 *pgpc = gpc;
947
948 return 0;
949
950fail:
951 if (tfc)
952 avtext_context_close(&tfc);
953 if (wctx && !tfc) // Only free wctx if tfc didn't take ownership of it
954 avtextwriter_context_close(&wctx);
955 av_freep(&gpc);
956
957 return ret;
958}
959
960
961int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph)
962{
963 GraphPrintContext *gpc = NULL;
964 AVTextFormatContext *tfc;
965 FilterGraphPriv *fgp = fgp_from_fg(fg);
966 AVBPrint *target_buf = &fgp->graph_print_buf;
967 int ret;
968
969 if (!fg || !fgp) {
970 av_log(NULL, AV_LOG_ERROR, "Invalid filter graph provided\n");
971 return AVERROR(EINVAL);
972 }
973
974 if (target_buf->len)
975 av_bprint_finalize(target_buf, NULL);
976
977 ret = init_graphprint(&gpc, target_buf);
978 if (ret)
979 return ret;
980
981 if (!gpc) {
982 av_log(NULL, AV_LOG_ERROR, "Failed to initialize graph print context\n");
983 return AVERROR(ENOMEM);
984 }
985
986 tfc = gpc->tfc;
987
988 // Due to the threading model each graph needs to print itself into a buffer
989 // from its own thread. The actual printing happens short before cleanup in ffmpeg.c
990 // where all graphs are assembled together. To make this work, we need to put the
991 // formatting context into the same state like it would be when printing all at once,
992 // so here we print the section headers and clear the buffer to get into the right state.
993 avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
994 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
995 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
996
997 av_bprint_clear(target_buf);
998
999 print_filtergraph_single(gpc, fg, graph);
1000
1001 if (gpc->is_diagram) {
1002 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
1003 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
1004 }
1005
1006 uninit_graphprint(gpc);
1007
1008 return 0;
1009}
1010
1011static int print_filtergraphs_priv(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
1012{
1013 GraphPrintContext *gpc = NULL;
1014 AVTextFormatContext *tfc;
1015 AVBPrint target_buf;
1016 int ret;
1017
1018 ret = init_graphprint(&gpc, &target_buf);
1019 if (ret)
1020 goto cleanup;
1021
1022 if (!gpc) {
1023 ret = AVERROR(ENOMEM);
1024 goto cleanup;
1025 }
1026
1027 tfc = gpc->tfc;
1028
1029 avtext_print_section_header(tfc, NULL, SECTION_ID_ROOT);
1030 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPHS);
1031
1032 for (int i = 0; i < nb_graphs; i++) {
1033 FilterGraphPriv *fgp = fgp_from_fg(graphs[i]);
1034 AVBPrint *graph_buf = &fgp->graph_print_buf;
1035
1036 if (graph_buf->len > 0) {
1037 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
1038 av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
1039 av_bprint_finalize(graph_buf, NULL);
1040 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
1041 }
1042 }
1043
1044 for (int n = 0; n < nb_ofiles; n++) {
1045 OutputFile *of = ofiles[n];
1046
1047 for (int i = 0; i < of->nb_streams; i++) {
1048 OutputStream *ost = of->streams[i];
1049
1050 if (ost->fg_simple) {
1051 FilterGraphPriv *fgp = fgp_from_fg(ost->fg_simple);
1052 AVBPrint *graph_buf = &fgp->graph_print_buf;
1053
1054 if (graph_buf->len > 0) {
1055 avtext_print_section_header(tfc, NULL, SECTION_ID_FILTERGRAPH);
1056 av_bprint_append_data(&target_buf, graph_buf->str, graph_buf->len);
1057 av_bprint_finalize(graph_buf, NULL);
1058 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPH
1059 }
1060 }
1061 }
1062 }
1063
1064 avtext_print_section_footer(tfc); // SECTION_ID_FILTERGRAPHS
1065
1066 print_streams(gpc, ifiles, nb_ifiles, ofiles, nb_ofiles);
1067
1068 avtext_print_section_footer(tfc); // SECTION_ID_ROOT
1069
1070 if (print_graphs_file) {
1071 AVIOContext *avio = NULL;
1072
1073 if (!strcmp(print_graphs_file, "-")) {
1074 printf("%s", target_buf.str);
1075 } else {
1076 ret = avio_open2(&avio, print_graphs_file, AVIO_FLAG_WRITE, NULL, NULL);
1077 if (ret < 0) {
1078 av_log(NULL, AV_LOG_ERROR, "Failed to open graph output file, \"%s\": %s\n", print_graphs_file, av_err2str(ret));
1079 goto cleanup;
1080 }
1081
1082 avio_write(avio, (const unsigned char *)target_buf.str, FFMIN(target_buf.len, target_buf.size - 1));
1083 avio_flush(avio);
1084
1085 if ((ret = avio_closep(&avio)) < 0)
1086 av_log(NULL, AV_LOG_ERROR, "Error closing graph output file, loss of information possible: %s\n", av_err2str(ret));
1087 }
1088 }
1089
1090 if (print_graphs)
1091 av_log(NULL, AV_LOG_INFO, "%s %c", target_buf.str, '\n');
1092
1093cleanup:
1094 // Properly clean up resources
1095 if (gpc)
1096 uninit_graphprint(gpc);
1097
1098 // Ensure the target buffer is properly finalized
1099 av_bprint_finalize(&target_buf, NULL);
1100
1101 return ret;
1102}
1103
1104int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
1105{
1106 return print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
1107}
fftools/graph/graphprint.h+30−0
@@ -0,0 +1,30 @@
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#ifndef FFTOOLS_GRAPH_GRAPHPRINT_H
22#define FFTOOLS_GRAPH_GRAPHPRINT_H
23
24#include "fftools/ffmpeg.h"
25
26int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles);
27
28int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
29
30#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
fftools/textformat/avtextformat.c+3−1
@@ -677,7 +677,7 @@ fail: · 676 unmodified lines
677 return ret;677 return ret;
678}678}
679 679
680static const AVTextFormatter *registered_formatters[7 + 1];680static const AVTextFormatter *registered_formatters[9 + 1];
681 681
682static void formatters_register_all(void)682static void formatters_register_all(void)
683{683{
@@ -694,6 +694,8 @@ static void formatters_register_all(void) · 10 unmodified lines
694 registered_formatters[4] = &avtextformatter_ini;694 registered_formatters[4] = &avtextformatter_ini;
695 registered_formatters[5] = &avtextformatter_json;695 registered_formatters[5] = &avtextformatter_json;
696 registered_formatters[6] = &avtextformatter_xml;696 registered_formatters[6] = &avtextformatter_xml;
697 registered_formatters[7] = &avtextformatter_mermaid;
698 registered_formatters[8] = &avtextformatter_mermaidhtml;
697}699}
698 700
699const AVTextFormatter *avtext_get_formatter_by_name(const char *name)701const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
fftools/textformat/avtextformat.h+29−0
@@ -31,6 +31,12 @@ · 30 unmodified lines
31 31
32#define SECTION_MAX_NB_CHILDREN 1132#define SECTION_MAX_NB_CHILDREN 11
33 33
34typedef struct AVTextFormatSectionContext {
35 char *context_id;
36 const char *context_type;
37 int context_flags;
38} AVTextFormatSectionContext;
39
34 40
35typedef struct AVTextFormatSection {41typedef struct AVTextFormatSection {
36 int id; ///< unique id identifying a section42 int id; ///< unique id identifying a section
@@ -42,6 +48,10 @@ typedef struct AVTextFormatSection { · 5 unmodified lines
42 /// For these sections the element_name field is mandatory.48 /// For these sections the element_name field is mandatory.
43#define AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE 8 ///< the section contains a type to distinguish multiple nested elements49#define AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE 8 ///< the section contains a type to distinguish multiple nested elements
44#define AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE 16 ///< the items in this array section should be numbered individually by type50#define AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE 16 ///< the items in this array section should be numbered individually by type
51#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE 32 ///< ...
52#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS 64 ///< ...
53#define AV_TEXTFORMAT_SECTION_PRINT_TAGS 128 ///< ...
54#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH 256 ///< ...
45 55
46 int flags;56 int flags;
47 const int children_ids[SECTION_MAX_NB_CHILDREN + 1]; ///< list of children section IDS, terminated by -157 const int children_ids[SECTION_MAX_NB_CHILDREN + 1]; ///< list of children section IDS, terminated by -1
@@ -50,12 +60,17 @@ typedef struct AVTextFormatSection { · 2 unmodified lines
50 AVDictionary *entries_to_show;60 AVDictionary *entries_to_show;
51 const char *(*get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined61 const char *(*get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined
52 int show_all_entries;62 int show_all_entries;
63 const char *id_key; ///< name of the key to be used as the id
64 const char *src_id_key; ///< name of the key to be used as the source id for diagram connections
65 const char *dest_id_key; ///< name of the key to be used as the target id for diagram connections
66 const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
53} AVTextFormatSection;67} AVTextFormatSection;
54 68
55typedef struct AVTextFormatContext AVTextFormatContext;69typedef struct AVTextFormatContext AVTextFormatContext;
56 70
57#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 171#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
58#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 272#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
73#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER 4
59 74
60typedef enum {75typedef enum {
61 AV_TEXTFORMAT_STRING_VALIDATION_FAIL,76 AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
@@ -64,6 +79,18 @@ typedef enum { · 2 unmodified lines
64 AV_TEXTFORMAT_STRING_VALIDATION_NB79 AV_TEXTFORMAT_STRING_VALIDATION_NB
65} StringValidation;80} StringValidation;
66 81
82typedef enum {
83 AV_TEXTFORMAT_LINKTYPE_SRCDEST,
84 AV_TEXTFORMAT_LINKTYPE_DESTSRC,
85 AV_TEXTFORMAT_LINKTYPE_BIDIR,
86 AV_TEXTFORMAT_LINKTYPE_NONDIR,
87 AV_TEXTFORMAT_LINKTYPE_HIDDEN,
88 AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
89 AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
90 AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
91 AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
92} AVTextFormatLinkType;
93
67typedef struct AVTextFormatter {94typedef struct AVTextFormatter {
68 const AVClass *priv_class; ///< private class of the formatter, if any95 const AVClass *priv_class; ///< private class of the formatter, if any
69 int priv_size; ///< private size for the formatter context96 int priv_size; ///< private size for the formatter context
@@ -166,5 +193,7 @@ extern const AVTextFormatter avtextformatter_flat; · 96 unmodified lines
166extern const AVTextFormatter avtextformatter_ini;193extern const AVTextFormatter avtextformatter_ini;
167extern const AVTextFormatter avtextformatter_json;194extern const AVTextFormatter avtextformatter_json;
168extern const AVTextFormatter avtextformatter_xml;195extern const AVTextFormatter avtextformatter_xml;
196extern const AVTextFormatter avtextformatter_mermaid;
197extern const AVTextFormatter avtextformatter_mermaidhtml;
169 198
170#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */199#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
fftools/textformat/tf_mermaid.c+658−0
@@ -0,0 +1,658 @@
1/*
2 * Copyright (c) The FFmpeg developers
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#include <limits.h>
22#include <stdarg.h>
23#include <stdint.h>
24#include <stdio.h>
25#include <string.h>
26
27#include "avtextformat.h"
28#include "tf_internal.h"
29#include "tf_mermaid.h"
30#include <libavutil/mem.h>
31#include <libavutil/avassert.h>
32#include <libavutil/bprint.h>
33#include <libavutil/opt.h>
34
35
36static const char *init_directive = ""
37 "%%{init: {"
38 "\"theme\": \"base\","
39 "\"curve\": \"monotoneX\","
40 "\"rankSpacing\": 10,"
41 "\"nodeSpacing\": 10,"
42 "\"themeCSS\": \"__###__\","
43 "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
44 "\"themeVariables\": { "
45 "\"clusterBkg\": \"white\", "
46 "\"primaryBorderColor\": \"gray\", "
47 "\"lineColor\": \"gray\", "
48 "\"secondaryTextColor\": \"gray\", "
49 "\"tertiaryBorderColor\": \"gray\", "
50 "\"primaryTextColor\": \"#666\", "
51 "\"secondaryTextColor\": \"red\" "
52 "},"
53 "\"flowchart\": { "
54 "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
55 "\"diagramPadding\": 20, "
56 "\"curve\": \"monotoneX\" "
57 "}"
58 " }}%%\n\n";
59
60static const char* init_directive_er = ""
61 "%%{init: {"
62 "\"theme\": \"base\","
63 "\"layout\": \"elk\","
64 "\"curve\": \"monotoneX\","
65 "\"rankSpacing\": 65,"
66 "\"nodeSpacing\": 60,"
67 "\"themeCSS\": \"__###__\","
68 "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
69 "\"themeVariables\": { "
70 "\"clusterBkg\": \"white\", "
71 "\"primaryBorderColor\": \"gray\", "
72 "\"lineColor\": \"gray\", "
73 "\"secondaryTextColor\": \"gray\", "
74 "\"tertiaryBorderColor\": \"gray\", "
75 "\"primaryTextColor\": \"#666\", "
76 "\"secondaryTextColor\": \"red\" "
77 "},"
78 "\"er\": { "
79 "\"diagramPadding\": 12, "
80 "\"entityPadding\": 4, "
81 "\"minEntityWidth\": 150, "
82 "\"minEntityHeight\": 20, "
83 "\"curve\": \"monotoneX\" "
84 "}"
85 " }}%%\n\n";
86
87static const char *theme_css_er = ""
88
89 // Variables
90 ".root { "
91 "--ff-colvideo: #6eaa7b; "
92 "--ff-colaudio: #477fb3; "
93 "--ff-colsubtitle: #ad76ab; "
94 "--ff-coltext: #666; "
95 "} "
96 " g.nodes g.node.default rect.basic.label-container, "
97 " g.nodes g.node.default path { "
98 " rx: 1; "
99 " ry: 1; "
100 " stroke-width: 1px !important; "
101 " stroke: #e9e9e9 !important; "
102 " fill: url(#ff-filtergradient) !important; "
103 " filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
104 " fill: white !important; "
105 " } "
106 " "
107 " .relationshipLine { "
108 " stroke: gray; "
109 " stroke-width: 1; "
110 " fill: none; "
111 " filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
112 " } "
113 " "
114 " g.node.default g.label.name foreignObject > div > span > p, "
115 " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
116 " font-size: 0.95rem; "
117 " font-weight: 500; "
118 " text-transform: uppercase; "
119 " min-width: 5.5rem; "
120 " margin-bottom: 0.5rem; "
121 " "
122 " } "
123 " "
124 " .edgePaths path { "
125 " marker-end: none; "
126 " marker-start: none; "
127 " "
128 "} ";
129
130
131/* Mermaid Graph output */
132
133typedef struct MermaidContext {
134 const AVClass *class;
135 AVDiagramConfig *diagram_config;
136 int subgraph_count;
137 int within_tag;
138 int indent_level;
139 int create_html;
140
141 // Options
142 int enable_link_colors; // Requires Mermaid 11.5
143
144 struct section_data {
145 const char *section_id;
146 const char *section_type;
147 const char *src_id;
148 const char *dest_id;
149 AVTextFormatLinkType link_type;
150 int current_is_textblock;
151 int current_is_stadium;
152 int subgraph_start_incomplete;
153 } section_data[SECTION_MAX_NB_LEVELS];
154
155 unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
156 AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
157 AVBPrint link_buf; ///< print buffer for writing diagram links
158 AVDictionary *link_dict;
159} MermaidContext;
160
161#undef OFFSET
162#define OFFSET(x) offsetof(MermaidContext, x)
163
164static const AVOption mermaid_options[] = {
165 { "link_coloring", "enable colored links (requires Mermaid >= 11.5)", OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
166 ////{"diagram_css", "CSS for the diagram", OFFSET(diagram_css), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
167 ////{"html_template", "Template HTML", OFFSET(html_template), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
168 { NULL },
169};
170
171DEFINE_FORMATTER_CLASS(mermaid);
172
173void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
174{
175 MermaidContext *mmc = tfc->priv;
176 mmc->diagram_config = diagram_config;
177}
178
179static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
180{
181 MermaidContext *mmc = tfc->priv;
182 AVBPrint buf;
183
184 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
185 av_bprintf(&buf, "%s--%s", src, dest);
186
187 if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
188 return 1;
189
190 av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
191
192 return 0;
193}
194
195static av_cold int mermaid_init(AVTextFormatContext *tfc)
196{
197 MermaidContext *mmc = tfc->priv;
198
199 av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
200
201 ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
202 return 0;
203}
204
205static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
206{
207 MermaidContext *mmc = tfc->priv;
208
209 int ret = mermaid_init(tfc);
210
211 if (ret < 0)
212 return ret;
213
214 mmc->create_html = 1;
215
216 return 0;
217}
218
219#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
220
221static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
222{
223 const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
224 const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
225
226 if (!section)
227 return;
228 AVBPrint *buf = &tfc->section_pbuf[tfc->level];
229 MermaidContext *mmc = tfc->priv;
230 const AVTextFormatSectionContext *sec_ctx = data;
231
232 if (tfc->level == 0) {
233 char *directive;
234 AVBPrint css_buf;
235 const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
236 char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
237 (void)theme_css_er;
238 ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
239 av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
240 av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
241 av_freep(&single_line_css);
242
243 directive = av_strireplace(diag_directive, "__###__", css_buf.str);
244 if (mmc->create_html) {
245 uint64_t length;
246 char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
247 if (!token_pos) {
248 av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
249 return;
250 }
251
252 length = token_pos - mmc->diagram_config->html_template;
253 for (uint64_t i = 0; i < length; i++)
254 writer_w8(tfc, mmc->diagram_config->html_template[i]);
255 }
256
257 writer_put_str(tfc, directive);
258 switch (mmc->diagram_config->diagram_type) {
259 case AV_DIAGRAMTYPE_GRAPH:
260 writer_put_str(tfc, "flowchart LR\n");
261 ////writer_put_str(tfc, " gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
262 writer_put_str(tfc, " gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%); \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%); \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%); \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
263 break;
264 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
265 writer_put_str(tfc, "erDiagram\n");
266 break;
267 }
268
269 return;
270 }
271
272 if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
273
274 struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
275 AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
276
277 if (parent_sec_data.subgraph_start_incomplete) {
278
279 if (parent_buf->len > 0)
280 writer_printf(tfc, "%s", parent_buf->str);
281
282 writer_put_str(tfc, "</div>\"]\n");
283
284 mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
285 }
286 }
287
288 av_freep(&mmc->section_data[tfc->level].section_id);
289 av_freep(&mmc->section_data[tfc->level].section_type);
290 av_freep(&mmc->section_data[tfc->level].src_id);
291 av_freep(&mmc->section_data[tfc->level].dest_id);
292 mmc->section_data[tfc->level].current_is_textblock = 0;
293 mmc->section_data[tfc->level].current_is_stadium = 0;
294 mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
295 mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
296
297 // NOTE: av_strdup() allocations aren't checked
298 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
299
300 av_bprint_clear(buf);
301 writer_put_str(tfc, "\n");
302
303 mmc->indent_level++;
304
305 if (sec_ctx->context_id) {
306 MM_INDENT();
307 writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
308 } else {
309 av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
310 }
311
312 mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
313 mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
314 }
315
316 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
317
318 av_bprint_clear(buf);
319 writer_put_str(tfc, "\n");
320
321 mmc->indent_level++;
322
323 if (sec_ctx->context_id) {
324
325 mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
326
327 switch (mmc->diagram_config->diagram_type) {
328 case AV_DIAGRAMTYPE_GRAPH:
329 if (sec_ctx->context_flags & 1) {
330
331 MM_INDENT();
332 writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
333 mmc->section_data[tfc->level].current_is_textblock = 1;
334 } else if (sec_ctx->context_flags & 2) {
335
336 MM_INDENT();
337 writer_printf(tfc, "%s([\"", sec_ctx->context_id);
338 mmc->section_data[tfc->level].current_is_stadium = 1;
339 } else {
340 MM_INDENT();
341 writer_printf(tfc, "%s(\"", sec_ctx->context_id);
342 }
343
344 break;
345 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
346 MM_INDENT();
347 writer_printf(tfc, "%s {\n", sec_ctx->context_id);
348 break;
349 }
350
351 } else {
352 av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
353 }
354
355 mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
356 }
357
358
359 if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
360
361 if (sec_ctx && sec_ctx->context_type)
362 writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
363 else
364 writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
365 }
366
367
368 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
369
370 av_bprint_clear(buf);
371 mmc->nb_link_captions[tfc->level] = 0;
372
373 if (sec_ctx && sec_ctx->context_type)
374 mmc->section_data[tfc->level].section_type = av_strdup(sec_ctx->context_type);
375
376 ////if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
377 //// AVBPrint buf;
378 //// av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
379 //// av_bprint_escape(&buf, section->get_type(data), NULL,
380 //// AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
381 //// writer_printf(tfc, " type=\"%s\"", buf.str);
382 }
383}
384
385static void mermaid_print_section_footer(AVTextFormatContext *tfc)
386{
387 MermaidContext *mmc = tfc->priv;
388 const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
389
390 if (!section)
391 return;
392 AVBPrint *buf = &tfc->section_pbuf[tfc->level];
393 struct section_data sec_data = mmc->section_data[tfc->level];
394
395 if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
396 writer_put_str(tfc, "</div>");
397
398 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
399
400 switch (mmc->diagram_config->diagram_type) {
401 case AV_DIAGRAMTYPE_GRAPH:
402
403 if (sec_data.current_is_textblock) {
404 writer_printf(tfc, "\"}\n", section->name);
405
406 if (sec_data.section_id) {
407 MM_INDENT();
408 writer_put_str(tfc, "class ");
409 writer_put_str(tfc, sec_data.section_id);
410 writer_put_str(tfc, " ff-");
411 writer_put_str(tfc, section->name);
412 writer_put_str(tfc, "\n");
413 }
414 } else if (sec_data.current_is_stadium) {
415 writer_printf(tfc, "\"]):::ff-%s\n", section->name);
416 } else {
417 writer_printf(tfc, "\"):::ff-%s\n", section->name);
418 }
419
420 break;
421 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
422 MM_INDENT();
423 writer_put_str(tfc, "}\n\n");
424 break;
425 }
426
427 mmc->indent_level--;
428
429 } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
430
431 MM_INDENT();
432 writer_put_str(tfc, "end\n");
433
434 if (sec_data.section_id) {
435 MM_INDENT();
436 writer_put_str(tfc, "class ");
437 writer_put_str(tfc, sec_data.section_id);
438 writer_put_str(tfc, " ff-");
439 writer_put_str(tfc, section->name);
440 writer_put_str(tfc, "\n");
441 }
442
443 mmc->indent_level--;
444 }
445
446 if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
447 if (sec_data.src_id && sec_data.dest_id
448 && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
449 switch (mmc->diagram_config->diagram_type) {
450 case AV_DIAGRAMTYPE_GRAPH:
451
452 if (sec_data.section_type && mmc->enable_link_colors)
453 av_bprintf(&mmc->link_buf, "\n %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
454 else
455 av_bprintf(&mmc->link_buf, "\n %s ==", sec_data.src_id);
456
457 if (buf->len > 0) {
458 av_bprintf(&mmc->link_buf, " \"%s", buf->str);
459
460 for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
461 av_bprintf(&mmc->link_buf, "<br>&nbsp;");
462
463 av_bprintf(&mmc->link_buf, "\" ==");
464 }
465
466 av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
467
468 break;
469 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
470
471
472 av_bprintf(&mmc->link_buf, "\n %s", sec_data.src_id);
473
474 switch (sec_data.link_type) {
475 case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
476 av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
477 break;
478 case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
479 av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
480 break;
481 case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
482 av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
483 break;
484 case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
485 av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
486 break;
487 default:
488 av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
489 break;
490 }
491
492 av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
493
494 break;
495 }
496
497 if (tfc->level == 0) {
498
499 writer_put_str(tfc, "\n");
500 if (mmc->create_html) {
501 char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
502 if (!token_pos) {
503 av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
504 return;
505 }
506 token_pos += strlen("__###__");
507 writer_put_str(tfc, token_pos);
508 }
509 }
510
511 if (tfc->level == 1) {
512
513 if (mmc->link_buf.len > 0) {
514 writer_put_str(tfc, mmc->link_buf.str);
515 av_bprint_clear(&mmc->link_buf);
516 }
517
518 writer_put_str(tfc, "\n");
519 }
520}
521
522static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
523 const char *str, int64_t num, const int is_int)
524{
525 MermaidContext *mmc = tfc->priv;
526 const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
527
528 if (!section)
529 return;
530
531 AVBPrint *buf = &tfc->section_pbuf[tfc->level];
532 struct section_data sec_data = mmc->section_data[tfc->level];
533 int exit = 0;
534
535 if (section->id_key && !strcmp(section->id_key, key)) {
536 mmc->section_data[tfc->level].section_id = av_strdup(str);
537 exit = 1;
538 }
539
540 if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
541 mmc->section_data[tfc->level].dest_id = av_strdup(str);
542 exit = 1;
543 }
544
545 if (section->src_id_key && !strcmp(section->src_id_key, key)) {
546 mmc->section_data[tfc->level].src_id = av_strdup(str);
547 exit = 1;
548 }
549
550 if (section->linktype_key && !strcmp(section->linktype_key, key)) {
551 mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
552 exit = 1;
553 }
554
555 //if (exit)
556 // return;
557
558 if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
559 || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
560
561 if (exit)
562 return;
563
564 switch (mmc->diagram_config->diagram_type) {
565 case AV_DIAGRAMTYPE_GRAPH:
566
567 if (is_int) {
568 writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
569 } else {
570 ////AVBPrint b;
571 ////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
572 const char *tmp = av_strireplace(str, "\"", "'");
573 ////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
574 writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
575 av_freep(&tmp);
576 }
577
578 break;
579 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
580
581 if (!is_int && str)
582 {
583 const char *col_type;
584
585 if (key[0] == '_')
586 return;
587
588 if (sec_data.section_id && !strcmp(str, sec_data.section_id))
589 col_type = "PK";
590 else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
591 col_type = "FK";
592 else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
593 col_type = "FK";
594 else
595 col_type = "";
596
597 MM_INDENT();
598
599 if (is_int)
600 writer_printf(tfc, " %s %"PRId64" %s\n", key, num, col_type);
601 else
602 writer_printf(tfc, " %s %s %s\n", key, str, col_type);
603 }
604 break;
605 }
606
607 } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
608
609 if (exit)
610 return;
611
612 if (buf->len > 0)
613 av_bprintf(buf, "%s", "<br>");
614
615 av_bprintf(buf, "");
616 if (is_int)
617 av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
618 else
619 av_bprintf(buf, "<span>%s</span>", str);
620
621 mmc->nb_link_captions[tfc->level]++;
622 }
623}
624
625static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
626{
627 mermaid_print_value(tfc, key, value, 0, 0);
628}
629
630static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
631{
632 mermaid_print_value(tfc, key, NULL, value, 1);
633}
634
635const AVTextFormatter avtextformatter_mermaid = {
636 .name = "mermaid",
637 .priv_size = sizeof(MermaidContext),
638 .init = mermaid_init,
639 .print_section_header = mermaid_print_section_header,
640 .print_section_footer = mermaid_print_section_footer,
641 .print_integer = mermaid_print_int,
642 .print_string = mermaid_print_str,
643 .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
644 .priv_class = &mermaid_class,
645};
646
647
648const AVTextFormatter avtextformatter_mermaidhtml = {
649 .name = "mermaidhtml",
650 .priv_size = sizeof(MermaidContext),
651 .init = mermaid_init_html,
652 .print_section_header = mermaid_print_section_header,
653 .print_section_footer = mermaid_print_section_footer,
654 .print_integer = mermaid_print_int,
655 .print_string = mermaid_print_str,
656 .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
657 .priv_class = &mermaid_class,
658};
fftools/textformat/tf_mermaid.h+41−0
@@ -0,0 +1,41 @@
1/*
2 * Copyright (c) The FFmpeg developers
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#ifndef FFTOOLS_TEXTFORMAT_TF_MERMAID_H
22#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
23
24typedef enum {
25 AV_DIAGRAMTYPE_GRAPH,
26 AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
27} AVDiagramType;
28
29typedef struct AVDiagramConfig {
30 AVDiagramType diagram_type;
31 const char *diagram_css;
32 const char *html_template;
33} AVDiagramConfig;
34
35
36void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
37
38void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
39
40
41#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t