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
Unified · Split
| @@ -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}) |
| 1395 | Set period at which encoding progress/statistics are updated. Default is 0.5 seconds. | 1395 | Set period at which encoding progress/statistics are updated. Default is 0.5 seconds. |
| 1396 | 1396 | ||
| 1397 | @item -print_graphs (@emph{global}) | ||
| 1398 | Prints execution graph details to stderr in the format set via -print_graphs_format. | ||
| 1399 | |||
| 1400 | @item -print_graphs_file @var{filename} (@emph{global}) | ||
| 1401 | Writes 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}) | ||
| 1404 | Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml) | ||
| 1405 | The default format is json. | ||
| 1406 | |||
| 1397 | @item -progress @var{url} (@emph{global}) | 1407 | @item -progress @var{url} (@emph{global}) |
| 1398 | Send program-friendly progress information to @var{url}. | 1408 | Send program-friendly progress information to @var{url}. |
| 1399 | 1409 | ||
| @@ -9,6 +9,8 @@ AVBASENAMES = ffmpeg ffplay ffprobe · 8 unmodified lines | |||
| 9 | ALLAVPROGS = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF)) | 9 | ALLAVPROGS = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF)) |
| 10 | ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF)) | 10 | ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF)) |
| 11 | 11 | ||
| 12 | include $(SRC_PATH)/fftools/resources/Makefile | ||
| 13 | |||
| 12 | OBJS-ffmpeg += \ | 14 | OBJS-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 | ||
| 25 | OBJS-ffprobe += \ | 40 | OBJS-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 | ||
| 37 | OBJS-ffplay += fftools/ffplay_renderer.o | 54 | OBJS-ffplay += fftools/ffplay_renderer.o |
| 38 | 55 | ||
| @@ -42,7 +59,7 @@ ifdef HAVE_GNU_WINDRES · 3 unmodified lines | |||
| 42 | OBJS-$(1) += fftools/fftoolsres.o | 59 | OBJS-$(1) += fftools/fftoolsres.o |
| 43 | endif | 60 | endif |
| 44 | $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1)) | 61 | $(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1)) |
| 45 | $$(OBJS-$(1)): | fftools fftools/textformat fftools/resources | 62 | $$(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 | |||
| 57 | OUTDIRS += fftools | 74 | OUTDIRS += fftools |
| 58 | OUTDIRS += fftools/textformat | 75 | OUTDIRS += fftools/textformat |
| 59 | OUTDIRS += fftools/resources | 76 | OUTDIRS += fftools/resources |
| 77 | OUTDIRS += fftools/graph | ||
| 60 | 78 | ||
| 61 | ifdef AVPROGS | 79 | ifdef AVPROGS |
| 62 | install: install-progs install-data | 80 | install: install-progs install-data |
| @@ -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 | ||
| 85 | const char program_name[] = "ffmpeg"; | 86 | const char program_name[] = "ffmpeg"; |
| 86 | const int program_birth_year = 2000; | 87 | const int program_birth_year = 2000; |
| @@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL }; · 221 unmodified lines | |||
| 308 | 309 | ||
| 309 | static void ffmpeg_cleanup(int ret) | 310 | static 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); |
| @@ -717,6 +717,9 @@ extern float max_error_rate; · 716 unmodified lines | |||
| 717 | extern char *filter_nbthreads; | 717 | extern char *filter_nbthreads; |
| 718 | extern int filter_complex_nbthreads; | 718 | extern int filter_complex_nbthreads; |
| 719 | extern int vstats_version; | 719 | extern int vstats_version; |
| 720 | extern int print_graphs; | ||
| 721 | extern char *print_graphs_file; | ||
| 722 | extern char *print_graphs_format; | ||
| 720 | extern int auto_conversion_filters; | 723 | extern int auto_conversion_filters; |
| 721 | 724 | ||
| 722 | extern const AVIOInterruptCB int_cb; | 725 | extern const AVIOInterruptCB int_cb; |
| @@ -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 | ||
| 2985 | finish: | 2986 | finish: |
| 2987 | |||
| 2988 | if (print_graphs || print_graphs_file) | ||
| 2989 | print_filtergraph(fg, fgt.graph); | ||
| 2990 | |||
| 2986 | // EOF is normal termination | 2991 | // EOF is normal termination |
| 2987 | if (ret == AVERROR_EOF) | 2992 | if (ret == AVERROR_EOF) |
| 2988 | ret = 0; | 2993 | ret = 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 | ||
| 51 | HWDevice *filter_hw_device; | 52 | HWDevice *filter_hw_device; |
| 52 | 53 | ||
| @@ -75,6 +76,9 @@ float max_error_rate = 2.0/3; · 22 unmodified lines | |||
| 75 | char *filter_nbthreads; | 76 | char *filter_nbthreads; |
| 76 | int filter_complex_nbthreads = 0; | 77 | int filter_complex_nbthreads = 0; |
| 77 | int vstats_version = 2; | 78 | int vstats_version = 2; |
| 79 | int print_graphs = 0; | ||
| 80 | char *print_graphs_file = NULL; | ||
| 81 | char *print_graphs_format = NULL; | ||
| 78 | int auto_conversion_filters = 1; | 82 | int auto_conversion_filters = 1; |
| 79 | int64_t stats_period = 500000; | 83 | int64_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 | #endif | 1741 | #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" }, |
| @@ -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 | |||
| 46 | typedef 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 | |||
| 77 | static 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 | |||
| 122 | typedef 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 | |||
| 158 | static atomic_int prefix_num = 0; | ||
| 159 | |||
| 160 | static 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 | |||
| 169 | static 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 | |||
| 201 | static 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 | |||
| 212 | static 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 | |||
| 247 | static 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 | |||
| 323 | static 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 | |||
| 330 | static 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 | |||
| 352 | static 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 | |||
| 377 | static const char *get_filterpad_name(const AVFilterPad *pad) | ||
| 378 | { | ||
| 379 | return pad ? avfilter_pad_get_name(pad, 0) : "pad"; | ||
| 380 | } | ||
| 381 | |||
| 382 | static 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 | |||
| 472 | static 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 | |||
| 478 | static 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 | |||
| 588 | static 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 | |||
| 855 | static 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 | |||
| 867 | static 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 | |||
| 950 | fail: | ||
| 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 | |||
| 961 | int 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 | |||
| 1011 | static 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 | |||
| 1093 | cleanup: | ||
| 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 | |||
| 1104 | int 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 | } | ||
| @@ -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 | |||
| 26 | int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles); | ||
| 27 | |||
| 28 | int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph); | ||
| 29 | |||
| 30 | #endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */ | ||
| @@ -677,7 +677,7 @@ fail: · 676 unmodified lines | |||
| 677 | return ret; | 677 | return ret; |
| 678 | } | 678 | } |
| 679 | 679 | ||
| 680 | static const AVTextFormatter *registered_formatters[7 + 1]; | 680 | static const AVTextFormatter *registered_formatters[9 + 1]; |
| 681 | 681 | ||
| 682 | static void formatters_register_all(void) | 682 | static 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 | ||
| 699 | const AVTextFormatter *avtext_get_formatter_by_name(const char *name) | 701 | const AVTextFormatter *avtext_get_formatter_by_name(const char *name) |
| @@ -31,6 +31,12 @@ · 30 unmodified lines | |||
| 31 | 31 | ||
| 32 | #define SECTION_MAX_NB_CHILDREN 11 | 32 | #define SECTION_MAX_NB_CHILDREN 11 |
| 33 | 33 | ||
| 34 | typedef struct AVTextFormatSectionContext { | ||
| 35 | char *context_id; | ||
| 36 | const char *context_type; | ||
| 37 | int context_flags; | ||
| 38 | } AVTextFormatSectionContext; | ||
| 39 | |||
| 34 | 40 | ||
| 35 | typedef struct AVTextFormatSection { | 41 | typedef struct AVTextFormatSection { |
| 36 | int id; ///< unique id identifying a section | 42 | 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 elements | 49 | #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 type | 50 | #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 -1 | 57 | 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 defined | 61 | 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 | ||
| 55 | typedef struct AVTextFormatContext AVTextFormatContext; | 69 | typedef struct AVTextFormatContext AVTextFormatContext; |
| 56 | 70 | ||
| 57 | #define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1 | 71 | #define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1 |
| 58 | #define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2 | 72 | #define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2 |
| 73 | #define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER 4 | ||
| 59 | 74 | ||
| 60 | typedef enum { | 75 | typedef 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_NB | 79 | AV_TEXTFORMAT_STRING_VALIDATION_NB |
| 65 | } StringValidation; | 80 | } StringValidation; |
| 66 | 81 | ||
| 82 | typedef 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 | |||
| 67 | typedef struct AVTextFormatter { | 94 | typedef struct AVTextFormatter { |
| 68 | const AVClass *priv_class; ///< private class of the formatter, if any | 95 | const AVClass *priv_class; ///< private class of the formatter, if any |
| 69 | int priv_size; ///< private size for the formatter context | 96 | int priv_size; ///< private size for the formatter context |
| @@ -166,5 +193,7 @@ extern const AVTextFormatter avtextformatter_flat; · 96 unmodified lines | |||
| 166 | extern const AVTextFormatter avtextformatter_ini; | 193 | extern const AVTextFormatter avtextformatter_ini; |
| 167 | extern const AVTextFormatter avtextformatter_json; | 194 | extern const AVTextFormatter avtextformatter_json; |
| 168 | extern const AVTextFormatter avtextformatter_xml; | 195 | extern const AVTextFormatter avtextformatter_xml; |
| 196 | extern const AVTextFormatter avtextformatter_mermaid; | ||
| 197 | extern const AVTextFormatter avtextformatter_mermaidhtml; | ||
| 169 | 198 | ||
| 170 | #endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */ | 199 | #endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */ |
| @@ -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 | |||
| 36 | static 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 | |||
| 60 | static 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 | |||
| 87 | static 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 | |||
| 133 | typedef 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 | |||
| 164 | static 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 | |||
| 171 | DEFINE_FORMATTER_CLASS(mermaid); | ||
| 172 | |||
| 173 | void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config) | ||
| 174 | { | ||
| 175 | MermaidContext *mmc = tfc->priv; | ||
| 176 | mmc->diagram_config = diagram_config; | ||
| 177 | } | ||
| 178 | |||
| 179 | static 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 | |||
| 195 | static 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 | |||
| 205 | static 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 | |||
| 221 | static 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 | |||
| 385 | static 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> "); | ||
| 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 | |||
| 522 | static 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 | |||
| 625 | static 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 | |||
| 630 | static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value) | ||
| 631 | { | ||
| 632 | mermaid_print_value(tfc, key, NULL, value, 1); | ||
| 633 | } | ||
| 634 | |||
| 635 | const 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 | |||
| 648 | const 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 | }; | ||
| @@ -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 | |||
| 24 | typedef enum { | ||
| 25 | AV_DIAGRAMTYPE_GRAPH, | ||
| 26 | AV_DIAGRAMTYPE_ENTITYRELATIONSHIP, | ||
| 27 | } AVDiagramType; | ||
| 28 | |||
| 29 | typedef struct AVDiagramConfig { | ||
| 30 | AVDiagramType diagram_type; | ||
| 31 | const char *diagram_css; | ||
| 32 | const char *html_template; | ||
| 33 | } AVDiagramConfig; | ||
| 34 | |||
| 35 | |||
| 36 | void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config); | ||
| 37 | |||
| 38 | void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template); | ||
| 39 | |||
| 40 | |||
| 41 | #endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */ | ||