You've already forked FFmpeg
mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2025-08-04 22:03:09 +02:00
fftools/graphprint: Add execution graph printing
The key benefits are: - Different to other graph printing methods, this is outputting: - all graphs with runtime state (including auto-inserted filters) - each graph with its inputs and outputs - all filters with their in- and output pads - all connections between all input- and output pads - for each connection: - the runtime-negotiated format and media type - the hw context - if video hw context, both: hw pixfmt + sw pixfmt - Output can either be printed to stdout or written to specified file - Output is machine-readable - Use the same output implementation as ffprobe, supporting multiple formats Signed-off-by: softworkz <softworkz@hotmail.com>
This commit is contained in:
@@ -1394,6 +1394,16 @@ It is on by default, to explicitly disable it you need to specify @code{-nostats
|
|||||||
@item -stats_period @var{time} (@emph{global})
|
@item -stats_period @var{time} (@emph{global})
|
||||||
Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
|
Set period at which encoding progress/statistics are updated. Default is 0.5 seconds.
|
||||||
|
|
||||||
|
@item -print_graphs (@emph{global})
|
||||||
|
Prints execution graph details to stderr in the format set via -print_graphs_format.
|
||||||
|
|
||||||
|
@item -print_graphs_file @var{filename} (@emph{global})
|
||||||
|
Writes execution graph details to the specified file in the format set via -print_graphs_format.
|
||||||
|
|
||||||
|
@item -print_graphs_format @var{format} (@emph{global})
|
||||||
|
Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
|
||||||
|
The default format is json.
|
||||||
|
|
||||||
@item -progress @var{url} (@emph{global})
|
@item -progress @var{url} (@emph{global})
|
||||||
Send program-friendly progress information to @var{url}.
|
Send program-friendly progress information to @var{url}.
|
||||||
|
|
||||||
|
@@ -9,6 +9,8 @@ AVBASENAMES = ffmpeg ffplay ffprobe
|
|||||||
ALLAVPROGS = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
|
ALLAVPROGS = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF))
|
||||||
ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
|
ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF))
|
||||||
|
|
||||||
|
include $(SRC_PATH)/fftools/resources/Makefile
|
||||||
|
|
||||||
OBJS-ffmpeg += \
|
OBJS-ffmpeg += \
|
||||||
fftools/ffmpeg_dec.o \
|
fftools/ffmpeg_dec.o \
|
||||||
fftools/ffmpeg_demux.o \
|
fftools/ffmpeg_demux.o \
|
||||||
@@ -19,8 +21,21 @@ OBJS-ffmpeg += \
|
|||||||
fftools/ffmpeg_mux_init.o \
|
fftools/ffmpeg_mux_init.o \
|
||||||
fftools/ffmpeg_opt.o \
|
fftools/ffmpeg_opt.o \
|
||||||
fftools/ffmpeg_sched.o \
|
fftools/ffmpeg_sched.o \
|
||||||
|
fftools/graph/graphprint.o \
|
||||||
fftools/sync_queue.o \
|
fftools/sync_queue.o \
|
||||||
fftools/thread_queue.o \
|
fftools/thread_queue.o \
|
||||||
|
fftools/textformat/avtextformat.o \
|
||||||
|
fftools/textformat/tf_compact.o \
|
||||||
|
fftools/textformat/tf_default.o \
|
||||||
|
fftools/textformat/tf_flat.o \
|
||||||
|
fftools/textformat/tf_ini.o \
|
||||||
|
fftools/textformat/tf_json.o \
|
||||||
|
fftools/textformat/tf_mermaid.o \
|
||||||
|
fftools/textformat/tf_xml.o \
|
||||||
|
fftools/textformat/tw_avio.o \
|
||||||
|
fftools/textformat/tw_buffer.o \
|
||||||
|
fftools/textformat/tw_stdout.o \
|
||||||
|
$(OBJS-resman) \
|
||||||
|
|
||||||
OBJS-ffprobe += \
|
OBJS-ffprobe += \
|
||||||
fftools/textformat/avtextformat.o \
|
fftools/textformat/avtextformat.o \
|
||||||
@@ -29,10 +44,12 @@ OBJS-ffprobe += \
|
|||||||
fftools/textformat/tf_flat.o \
|
fftools/textformat/tf_flat.o \
|
||||||
fftools/textformat/tf_ini.o \
|
fftools/textformat/tf_ini.o \
|
||||||
fftools/textformat/tf_json.o \
|
fftools/textformat/tf_json.o \
|
||||||
|
fftools/textformat/tf_mermaid.o \
|
||||||
fftools/textformat/tf_xml.o \
|
fftools/textformat/tf_xml.o \
|
||||||
fftools/textformat/tw_avio.o \
|
fftools/textformat/tw_avio.o \
|
||||||
fftools/textformat/tw_buffer.o \
|
fftools/textformat/tw_buffer.o \
|
||||||
fftools/textformat/tw_stdout.o \
|
fftools/textformat/tw_stdout.o \
|
||||||
|
$(OBJS-resman) \
|
||||||
|
|
||||||
OBJS-ffplay += fftools/ffplay_renderer.o
|
OBJS-ffplay += fftools/ffplay_renderer.o
|
||||||
|
|
||||||
@@ -42,7 +59,7 @@ ifdef HAVE_GNU_WINDRES
|
|||||||
OBJS-$(1) += fftools/fftoolsres.o
|
OBJS-$(1) += fftools/fftoolsres.o
|
||||||
endif
|
endif
|
||||||
$(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
|
$(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1))
|
||||||
$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources
|
$$(OBJS-$(1)): | fftools fftools/textformat fftools/resources fftools/graph
|
||||||
$$(OBJS-$(1)): CFLAGS += $(CFLAGS-$(1))
|
$$(OBJS-$(1)): CFLAGS += $(CFLAGS-$(1))
|
||||||
$(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
|
$(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1))
|
||||||
$(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
|
$(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1))
|
||||||
@@ -57,6 +74,7 @@ fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools
|
|||||||
OUTDIRS += fftools
|
OUTDIRS += fftools
|
||||||
OUTDIRS += fftools/textformat
|
OUTDIRS += fftools/textformat
|
||||||
OUTDIRS += fftools/resources
|
OUTDIRS += fftools/resources
|
||||||
|
OUTDIRS += fftools/graph
|
||||||
|
|
||||||
ifdef AVPROGS
|
ifdef AVPROGS
|
||||||
install: install-progs install-data
|
install: install-progs install-data
|
||||||
|
@@ -81,6 +81,7 @@
|
|||||||
#include "ffmpeg.h"
|
#include "ffmpeg.h"
|
||||||
#include "ffmpeg_sched.h"
|
#include "ffmpeg_sched.h"
|
||||||
#include "ffmpeg_utils.h"
|
#include "ffmpeg_utils.h"
|
||||||
|
#include "graph/graphprint.h"
|
||||||
|
|
||||||
const char program_name[] = "ffmpeg";
|
const char program_name[] = "ffmpeg";
|
||||||
const int program_birth_year = 2000;
|
const int program_birth_year = 2000;
|
||||||
@@ -308,6 +309,9 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
|
|||||||
|
|
||||||
static void ffmpeg_cleanup(int ret)
|
static void ffmpeg_cleanup(int ret)
|
||||||
{
|
{
|
||||||
|
if (print_graphs || print_graphs_file)
|
||||||
|
print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
|
||||||
|
|
||||||
if (do_benchmark) {
|
if (do_benchmark) {
|
||||||
int64_t maxrss = getmaxrss() / 1024;
|
int64_t maxrss = getmaxrss() / 1024;
|
||||||
av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
|
av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss);
|
||||||
|
@@ -717,6 +717,9 @@ extern float max_error_rate;
|
|||||||
extern char *filter_nbthreads;
|
extern char *filter_nbthreads;
|
||||||
extern int filter_complex_nbthreads;
|
extern int filter_complex_nbthreads;
|
||||||
extern int vstats_version;
|
extern int vstats_version;
|
||||||
|
extern int print_graphs;
|
||||||
|
extern char *print_graphs_file;
|
||||||
|
extern char *print_graphs_format;
|
||||||
extern int auto_conversion_filters;
|
extern int auto_conversion_filters;
|
||||||
|
|
||||||
extern const AVIOInterruptCB int_cb;
|
extern const AVIOInterruptCB int_cb;
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "ffmpeg.h"
|
#include "ffmpeg.h"
|
||||||
#include "ffmpeg_filter.h"
|
#include "ffmpeg_filter.h"
|
||||||
|
#include "graph/graphprint.h"
|
||||||
|
|
||||||
#include "libavfilter/avfilter.h"
|
#include "libavfilter/avfilter.h"
|
||||||
#include "libavfilter/buffersink.h"
|
#include "libavfilter/buffersink.h"
|
||||||
@@ -2983,6 +2984,10 @@ read_frames:
|
|||||||
}
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
|
|
||||||
|
if (print_graphs || print_graphs_file)
|
||||||
|
print_filtergraph(fg, fgt.graph);
|
||||||
|
|
||||||
// EOF is normal termination
|
// EOF is normal termination
|
||||||
if (ret == AVERROR_EOF)
|
if (ret == AVERROR_EOF)
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
@@ -47,6 +47,7 @@
|
|||||||
#include "libavutil/opt.h"
|
#include "libavutil/opt.h"
|
||||||
#include "libavutil/parseutils.h"
|
#include "libavutil/parseutils.h"
|
||||||
#include "libavutil/stereo3d.h"
|
#include "libavutil/stereo3d.h"
|
||||||
|
#include "graph/graphprint.h"
|
||||||
|
|
||||||
HWDevice *filter_hw_device;
|
HWDevice *filter_hw_device;
|
||||||
|
|
||||||
@@ -75,6 +76,9 @@ float max_error_rate = 2.0/3;
|
|||||||
char *filter_nbthreads;
|
char *filter_nbthreads;
|
||||||
int filter_complex_nbthreads = 0;
|
int filter_complex_nbthreads = 0;
|
||||||
int vstats_version = 2;
|
int vstats_version = 2;
|
||||||
|
int print_graphs = 0;
|
||||||
|
char *print_graphs_file = NULL;
|
||||||
|
char *print_graphs_format = NULL;
|
||||||
int auto_conversion_filters = 1;
|
int auto_conversion_filters = 1;
|
||||||
int64_t stats_period = 500000;
|
int64_t stats_period = 500000;
|
||||||
|
|
||||||
@@ -1735,6 +1739,15 @@ const OptionDef options[] = {
|
|||||||
{ .func_arg = opt_filter_complex_script },
|
{ .func_arg = opt_filter_complex_script },
|
||||||
"deprecated, use -/filter_complex instead", "filename" },
|
"deprecated, use -/filter_complex instead", "filename" },
|
||||||
#endif
|
#endif
|
||||||
|
{ "print_graphs", OPT_TYPE_BOOL, 0,
|
||||||
|
{ &print_graphs },
|
||||||
|
"print execution graph data to stderr" },
|
||||||
|
{ "print_graphs_file", OPT_TYPE_STRING, 0,
|
||||||
|
{ &print_graphs_file },
|
||||||
|
"write execution graph data to the specified file", "filename" },
|
||||||
|
{ "print_graphs_format", OPT_TYPE_STRING, 0,
|
||||||
|
{ &print_graphs_format },
|
||||||
|
"set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)", "format" },
|
||||||
{ "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
|
{ "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
|
||||||
{ &auto_conversion_filters },
|
{ &auto_conversion_filters },
|
||||||
"enable automatic conversion filters globally" },
|
"enable automatic conversion filters globally" },
|
||||||
|
1107
fftools/graph/graphprint.c
Normal file
1107
fftools/graph/graphprint.c
Normal file
File diff suppressed because it is too large
Load Diff
30
fftools/graph/graphprint.h
Normal file
30
fftools/graph/graphprint.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2025 - softworkz
|
||||||
|
*
|
||||||
|
* This file is part of FFmpeg.
|
||||||
|
*
|
||||||
|
* FFmpeg is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* FFmpeg is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with FFmpeg; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FFTOOLS_GRAPH_GRAPHPRINT_H
|
||||||
|
#define FFTOOLS_GRAPH_GRAPHPRINT_H
|
||||||
|
|
||||||
|
#include "fftools/ffmpeg.h"
|
||||||
|
|
||||||
|
int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles);
|
||||||
|
|
||||||
|
int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
|
||||||
|
|
||||||
|
#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
|
@@ -677,7 +677,7 @@ fail:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const AVTextFormatter *registered_formatters[7 + 1];
|
static const AVTextFormatter *registered_formatters[9 + 1];
|
||||||
|
|
||||||
static void formatters_register_all(void)
|
static void formatters_register_all(void)
|
||||||
{
|
{
|
||||||
@@ -694,6 +694,8 @@ static void formatters_register_all(void)
|
|||||||
registered_formatters[4] = &avtextformatter_ini;
|
registered_formatters[4] = &avtextformatter_ini;
|
||||||
registered_formatters[5] = &avtextformatter_json;
|
registered_formatters[5] = &avtextformatter_json;
|
||||||
registered_formatters[6] = &avtextformatter_xml;
|
registered_formatters[6] = &avtextformatter_xml;
|
||||||
|
registered_formatters[7] = &avtextformatter_mermaid;
|
||||||
|
registered_formatters[8] = &avtextformatter_mermaidhtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
|
const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
|
||||||
|
@@ -31,6 +31,12 @@
|
|||||||
|
|
||||||
#define SECTION_MAX_NB_CHILDREN 11
|
#define SECTION_MAX_NB_CHILDREN 11
|
||||||
|
|
||||||
|
typedef struct AVTextFormatSectionContext {
|
||||||
|
char *context_id;
|
||||||
|
const char *context_type;
|
||||||
|
int context_flags;
|
||||||
|
} AVTextFormatSectionContext;
|
||||||
|
|
||||||
|
|
||||||
typedef struct AVTextFormatSection {
|
typedef struct AVTextFormatSection {
|
||||||
int id; ///< unique id identifying a section
|
int id; ///< unique id identifying a section
|
||||||
@@ -42,6 +48,10 @@ typedef struct AVTextFormatSection {
|
|||||||
/// For these sections the element_name field is mandatory.
|
/// For these sections the element_name field is mandatory.
|
||||||
#define AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE 8 ///< the section contains a type to distinguish multiple nested elements
|
#define AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE 8 ///< the section contains a type to distinguish multiple nested elements
|
||||||
#define AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE 16 ///< the items in this array section should be numbered individually by type
|
#define AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE 16 ///< the items in this array section should be numbered individually by type
|
||||||
|
#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE 32 ///< ...
|
||||||
|
#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS 64 ///< ...
|
||||||
|
#define AV_TEXTFORMAT_SECTION_PRINT_TAGS 128 ///< ...
|
||||||
|
#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH 256 ///< ...
|
||||||
|
|
||||||
int flags;
|
int flags;
|
||||||
const int children_ids[SECTION_MAX_NB_CHILDREN + 1]; ///< list of children section IDS, terminated by -1
|
const int children_ids[SECTION_MAX_NB_CHILDREN + 1]; ///< list of children section IDS, terminated by -1
|
||||||
@@ -50,12 +60,17 @@ typedef struct AVTextFormatSection {
|
|||||||
AVDictionary *entries_to_show;
|
AVDictionary *entries_to_show;
|
||||||
const char *(*get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined
|
const char *(*get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined
|
||||||
int show_all_entries;
|
int show_all_entries;
|
||||||
|
const char *id_key; ///< name of the key to be used as the id
|
||||||
|
const char *src_id_key; ///< name of the key to be used as the source id for diagram connections
|
||||||
|
const char *dest_id_key; ///< name of the key to be used as the target id for diagram connections
|
||||||
|
const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
|
||||||
} AVTextFormatSection;
|
} AVTextFormatSection;
|
||||||
|
|
||||||
typedef struct AVTextFormatContext AVTextFormatContext;
|
typedef struct AVTextFormatContext AVTextFormatContext;
|
||||||
|
|
||||||
#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
|
#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
|
||||||
#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
|
#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
|
||||||
|
#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER 4
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
|
AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
|
||||||
@@ -64,6 +79,18 @@ typedef enum {
|
|||||||
AV_TEXTFORMAT_STRING_VALIDATION_NB
|
AV_TEXTFORMAT_STRING_VALIDATION_NB
|
||||||
} StringValidation;
|
} StringValidation;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
AV_TEXTFORMAT_LINKTYPE_SRCDEST,
|
||||||
|
AV_TEXTFORMAT_LINKTYPE_DESTSRC,
|
||||||
|
AV_TEXTFORMAT_LINKTYPE_BIDIR,
|
||||||
|
AV_TEXTFORMAT_LINKTYPE_NONDIR,
|
||||||
|
AV_TEXTFORMAT_LINKTYPE_HIDDEN,
|
||||||
|
AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
|
||||||
|
AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
|
||||||
|
AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
|
||||||
|
AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
|
||||||
|
} AVTextFormatLinkType;
|
||||||
|
|
||||||
typedef struct AVTextFormatter {
|
typedef struct AVTextFormatter {
|
||||||
const AVClass *priv_class; ///< private class of the formatter, if any
|
const AVClass *priv_class; ///< private class of the formatter, if any
|
||||||
int priv_size; ///< private size for the formatter context
|
int priv_size; ///< private size for the formatter context
|
||||||
@@ -166,5 +193,7 @@ extern const AVTextFormatter avtextformatter_flat;
|
|||||||
extern const AVTextFormatter avtextformatter_ini;
|
extern const AVTextFormatter avtextformatter_ini;
|
||||||
extern const AVTextFormatter avtextformatter_json;
|
extern const AVTextFormatter avtextformatter_json;
|
||||||
extern const AVTextFormatter avtextformatter_xml;
|
extern const AVTextFormatter avtextformatter_xml;
|
||||||
|
extern const AVTextFormatter avtextformatter_mermaid;
|
||||||
|
extern const AVTextFormatter avtextformatter_mermaidhtml;
|
||||||
|
|
||||||
#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
|
#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
|
||||||
|
658
fftools/textformat/tf_mermaid.c
Normal file
658
fftools/textformat/tf_mermaid.c
Normal file
@@ -0,0 +1,658 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) The FFmpeg developers
|
||||||
|
*
|
||||||
|
* This file is part of FFmpeg.
|
||||||
|
*
|
||||||
|
* FFmpeg is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* FFmpeg is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with FFmpeg; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "avtextformat.h"
|
||||||
|
#include "tf_internal.h"
|
||||||
|
#include "tf_mermaid.h"
|
||||||
|
#include <libavutil/mem.h>
|
||||||
|
#include <libavutil/avassert.h>
|
||||||
|
#include <libavutil/bprint.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
|
||||||
|
|
||||||
|
static const char *init_directive = ""
|
||||||
|
"%%{init: {"
|
||||||
|
"\"theme\": \"base\","
|
||||||
|
"\"curve\": \"monotoneX\","
|
||||||
|
"\"rankSpacing\": 10,"
|
||||||
|
"\"nodeSpacing\": 10,"
|
||||||
|
"\"themeCSS\": \"__###__\","
|
||||||
|
"\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
|
||||||
|
"\"themeVariables\": { "
|
||||||
|
"\"clusterBkg\": \"white\", "
|
||||||
|
"\"primaryBorderColor\": \"gray\", "
|
||||||
|
"\"lineColor\": \"gray\", "
|
||||||
|
"\"secondaryTextColor\": \"gray\", "
|
||||||
|
"\"tertiaryBorderColor\": \"gray\", "
|
||||||
|
"\"primaryTextColor\": \"#666\", "
|
||||||
|
"\"secondaryTextColor\": \"red\" "
|
||||||
|
"},"
|
||||||
|
"\"flowchart\": { "
|
||||||
|
"\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
|
||||||
|
"\"diagramPadding\": 20, "
|
||||||
|
"\"curve\": \"monotoneX\" "
|
||||||
|
"}"
|
||||||
|
" }}%%\n\n";
|
||||||
|
|
||||||
|
static const char* init_directive_er = ""
|
||||||
|
"%%{init: {"
|
||||||
|
"\"theme\": \"base\","
|
||||||
|
"\"layout\": \"elk\","
|
||||||
|
"\"curve\": \"monotoneX\","
|
||||||
|
"\"rankSpacing\": 65,"
|
||||||
|
"\"nodeSpacing\": 60,"
|
||||||
|
"\"themeCSS\": \"__###__\","
|
||||||
|
"\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
|
||||||
|
"\"themeVariables\": { "
|
||||||
|
"\"clusterBkg\": \"white\", "
|
||||||
|
"\"primaryBorderColor\": \"gray\", "
|
||||||
|
"\"lineColor\": \"gray\", "
|
||||||
|
"\"secondaryTextColor\": \"gray\", "
|
||||||
|
"\"tertiaryBorderColor\": \"gray\", "
|
||||||
|
"\"primaryTextColor\": \"#666\", "
|
||||||
|
"\"secondaryTextColor\": \"red\" "
|
||||||
|
"},"
|
||||||
|
"\"er\": { "
|
||||||
|
"\"diagramPadding\": 12, "
|
||||||
|
"\"entityPadding\": 4, "
|
||||||
|
"\"minEntityWidth\": 150, "
|
||||||
|
"\"minEntityHeight\": 20, "
|
||||||
|
"\"curve\": \"monotoneX\" "
|
||||||
|
"}"
|
||||||
|
" }}%%\n\n";
|
||||||
|
|
||||||
|
static const char *theme_css_er = ""
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
".root { "
|
||||||
|
"--ff-colvideo: #6eaa7b; "
|
||||||
|
"--ff-colaudio: #477fb3; "
|
||||||
|
"--ff-colsubtitle: #ad76ab; "
|
||||||
|
"--ff-coltext: #666; "
|
||||||
|
"} "
|
||||||
|
" g.nodes g.node.default rect.basic.label-container, "
|
||||||
|
" g.nodes g.node.default path { "
|
||||||
|
" rx: 1; "
|
||||||
|
" ry: 1; "
|
||||||
|
" stroke-width: 1px !important; "
|
||||||
|
" stroke: #e9e9e9 !important; "
|
||||||
|
" fill: url(#ff-filtergradient) !important; "
|
||||||
|
" filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
|
||||||
|
" fill: white !important; "
|
||||||
|
" } "
|
||||||
|
" "
|
||||||
|
" .relationshipLine { "
|
||||||
|
" stroke: gray; "
|
||||||
|
" stroke-width: 1; "
|
||||||
|
" fill: none; "
|
||||||
|
" filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
|
||||||
|
" } "
|
||||||
|
" "
|
||||||
|
" g.node.default g.label.name foreignObject > div > span > p, "
|
||||||
|
" g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
|
||||||
|
" font-size: 0.95rem; "
|
||||||
|
" font-weight: 500; "
|
||||||
|
" text-transform: uppercase; "
|
||||||
|
" min-width: 5.5rem; "
|
||||||
|
" margin-bottom: 0.5rem; "
|
||||||
|
" "
|
||||||
|
" } "
|
||||||
|
" "
|
||||||
|
" .edgePaths path { "
|
||||||
|
" marker-end: none; "
|
||||||
|
" marker-start: none; "
|
||||||
|
" "
|
||||||
|
"} ";
|
||||||
|
|
||||||
|
|
||||||
|
/* Mermaid Graph output */
|
||||||
|
|
||||||
|
typedef struct MermaidContext {
|
||||||
|
const AVClass *class;
|
||||||
|
AVDiagramConfig *diagram_config;
|
||||||
|
int subgraph_count;
|
||||||
|
int within_tag;
|
||||||
|
int indent_level;
|
||||||
|
int create_html;
|
||||||
|
|
||||||
|
// Options
|
||||||
|
int enable_link_colors; // Requires Mermaid 11.5
|
||||||
|
|
||||||
|
struct section_data {
|
||||||
|
const char *section_id;
|
||||||
|
const char *section_type;
|
||||||
|
const char *src_id;
|
||||||
|
const char *dest_id;
|
||||||
|
AVTextFormatLinkType link_type;
|
||||||
|
int current_is_textblock;
|
||||||
|
int current_is_stadium;
|
||||||
|
int subgraph_start_incomplete;
|
||||||
|
} section_data[SECTION_MAX_NB_LEVELS];
|
||||||
|
|
||||||
|
unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
|
||||||
|
AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
|
||||||
|
AVBPrint link_buf; ///< print buffer for writing diagram links
|
||||||
|
AVDictionary *link_dict;
|
||||||
|
} MermaidContext;
|
||||||
|
|
||||||
|
#undef OFFSET
|
||||||
|
#define OFFSET(x) offsetof(MermaidContext, x)
|
||||||
|
|
||||||
|
static const AVOption mermaid_options[] = {
|
||||||
|
{ "link_coloring", "enable colored links (requires Mermaid >= 11.5)", OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
|
||||||
|
////{"diagram_css", "CSS for the diagram", OFFSET(diagram_css), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
|
||||||
|
////{"html_template", "Template HTML", OFFSET(html_template), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
|
||||||
|
{ NULL },
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_FORMATTER_CLASS(mermaid);
|
||||||
|
|
||||||
|
void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
|
||||||
|
{
|
||||||
|
MermaidContext *mmc = tfc->priv;
|
||||||
|
mmc->diagram_config = diagram_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
|
||||||
|
{
|
||||||
|
MermaidContext *mmc = tfc->priv;
|
||||||
|
AVBPrint buf;
|
||||||
|
|
||||||
|
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||||
|
av_bprintf(&buf, "%s--%s", src, dest);
|
||||||
|
|
||||||
|
if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static av_cold int mermaid_init(AVTextFormatContext *tfc)
|
||||||
|
{
|
||||||
|
MermaidContext *mmc = tfc->priv;
|
||||||
|
|
||||||
|
av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||||
|
|
||||||
|
////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
|
||||||
|
{
|
||||||
|
MermaidContext *mmc = tfc->priv;
|
||||||
|
|
||||||
|
int ret = mermaid_init(tfc);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
mmc->create_html = 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
|
||||||
|
|
||||||
|
static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
|
||||||
|
{
|
||||||
|
const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
|
||||||
|
const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
|
||||||
|
|
||||||
|
if (!section)
|
||||||
|
return;
|
||||||
|
AVBPrint *buf = &tfc->section_pbuf[tfc->level];
|
||||||
|
MermaidContext *mmc = tfc->priv;
|
||||||
|
const AVTextFormatSectionContext *sec_ctx = data;
|
||||||
|
|
||||||
|
if (tfc->level == 0) {
|
||||||
|
char *directive;
|
||||||
|
AVBPrint css_buf;
|
||||||
|
const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
|
||||||
|
char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
|
||||||
|
(void)theme_css_er;
|
||||||
|
////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
|
||||||
|
av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||||
|
av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
|
||||||
|
av_freep(&single_line_css);
|
||||||
|
|
||||||
|
directive = av_strireplace(diag_directive, "__###__", css_buf.str);
|
||||||
|
if (mmc->create_html) {
|
||||||
|
uint64_t length;
|
||||||
|
char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
|
||||||
|
if (!token_pos) {
|
||||||
|
av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = token_pos - mmc->diagram_config->html_template;
|
||||||
|
for (uint64_t i = 0; i < length; i++)
|
||||||
|
writer_w8(tfc, mmc->diagram_config->html_template[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer_put_str(tfc, directive);
|
||||||
|
switch (mmc->diagram_config->diagram_type) {
|
||||||
|
case AV_DIAGRAMTYPE_GRAPH:
|
||||||
|
writer_put_str(tfc, "flowchart LR\n");
|
||||||
|
////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");
|
||||||
|
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");
|
||||||
|
break;
|
||||||
|
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||||
|
writer_put_str(tfc, "erDiagram\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
|
||||||
|
|
||||||
|
struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
|
||||||
|
AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
|
||||||
|
|
||||||
|
if (parent_sec_data.subgraph_start_incomplete) {
|
||||||
|
|
||||||
|
if (parent_buf->len > 0)
|
||||||
|
writer_printf(tfc, "%s", parent_buf->str);
|
||||||
|
|
||||||
|
writer_put_str(tfc, "</div>\"]\n");
|
||||||
|
|
||||||
|
mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
av_freep(&mmc->section_data[tfc->level].section_id);
|
||||||
|
av_freep(&mmc->section_data[tfc->level].section_type);
|
||||||
|
av_freep(&mmc->section_data[tfc->level].src_id);
|
||||||
|
av_freep(&mmc->section_data[tfc->level].dest_id);
|
||||||
|
mmc->section_data[tfc->level].current_is_textblock = 0;
|
||||||
|
mmc->section_data[tfc->level].current_is_stadium = 0;
|
||||||
|
mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
|
||||||
|
mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
|
||||||
|
|
||||||
|
// NOTE: av_strdup() allocations aren't checked
|
||||||
|
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
|
||||||
|
|
||||||
|
av_bprint_clear(buf);
|
||||||
|
writer_put_str(tfc, "\n");
|
||||||
|
|
||||||
|
mmc->indent_level++;
|
||||||
|
|
||||||
|
if (sec_ctx->context_id) {
|
||||||
|
MM_INDENT();
|
||||||
|
writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
|
||||||
|
} else {
|
||||||
|
av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
|
||||||
|
mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
|
||||||
|
|
||||||
|
av_bprint_clear(buf);
|
||||||
|
writer_put_str(tfc, "\n");
|
||||||
|
|
||||||
|
mmc->indent_level++;
|
||||||
|
|
||||||
|
if (sec_ctx->context_id) {
|
||||||
|
|
||||||
|
mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
|
||||||
|
|
||||||
|
switch (mmc->diagram_config->diagram_type) {
|
||||||
|
case AV_DIAGRAMTYPE_GRAPH:
|
||||||
|
if (sec_ctx->context_flags & 1) {
|
||||||
|
|
||||||
|
MM_INDENT();
|
||||||
|
writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
|
||||||
|
mmc->section_data[tfc->level].current_is_textblock = 1;
|
||||||
|
} else if (sec_ctx->context_flags & 2) {
|
||||||
|
|
||||||
|
MM_INDENT();
|
||||||
|
writer_printf(tfc, "%s([\"", sec_ctx->context_id);
|
||||||
|
mmc->section_data[tfc->level].current_is_stadium = 1;
|
||||||
|
} else {
|
||||||
|
MM_INDENT();
|
||||||
|
writer_printf(tfc, "%s(\"", sec_ctx->context_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||||
|
MM_INDENT();
|
||||||
|
writer_printf(tfc, "%s {\n", sec_ctx->context_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
mmc->section_data[tfc->level].section_id = av_strdup(sec_ctx->context_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
|
||||||
|
|
||||||
|
if (sec_ctx && sec_ctx->context_type)
|
||||||
|
writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
|
||||||
|
else
|
||||||
|
writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
|
||||||
|
|
||||||
|
av_bprint_clear(buf);
|
||||||
|
mmc->nb_link_captions[tfc->level] = 0;
|
||||||
|
|
||||||
|
if (sec_ctx && sec_ctx->context_type)
|
||||||
|
mmc->section_data[tfc->level].section_type = av_strdup(sec_ctx->context_type);
|
||||||
|
|
||||||
|
////if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
|
||||||
|
//// AVBPrint buf;
|
||||||
|
//// av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
|
||||||
|
//// av_bprint_escape(&buf, section->get_type(data), NULL,
|
||||||
|
//// AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
|
||||||
|
//// writer_printf(tfc, " type=\"%s\"", buf.str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mermaid_print_section_footer(AVTextFormatContext *tfc)
|
||||||
|
{
|
||||||
|
MermaidContext *mmc = tfc->priv;
|
||||||
|
const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
|
||||||
|
|
||||||
|
if (!section)
|
||||||
|
return;
|
||||||
|
AVBPrint *buf = &tfc->section_pbuf[tfc->level];
|
||||||
|
struct section_data sec_data = mmc->section_data[tfc->level];
|
||||||
|
|
||||||
|
if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
|
||||||
|
writer_put_str(tfc, "</div>");
|
||||||
|
|
||||||
|
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
|
||||||
|
|
||||||
|
switch (mmc->diagram_config->diagram_type) {
|
||||||
|
case AV_DIAGRAMTYPE_GRAPH:
|
||||||
|
|
||||||
|
if (sec_data.current_is_textblock) {
|
||||||
|
writer_printf(tfc, "\"}\n", section->name);
|
||||||
|
|
||||||
|
if (sec_data.section_id) {
|
||||||
|
MM_INDENT();
|
||||||
|
writer_put_str(tfc, "class ");
|
||||||
|
writer_put_str(tfc, sec_data.section_id);
|
||||||
|
writer_put_str(tfc, " ff-");
|
||||||
|
writer_put_str(tfc, section->name);
|
||||||
|
writer_put_str(tfc, "\n");
|
||||||
|
}
|
||||||
|
} else if (sec_data.current_is_stadium) {
|
||||||
|
writer_printf(tfc, "\"]):::ff-%s\n", section->name);
|
||||||
|
} else {
|
||||||
|
writer_printf(tfc, "\"):::ff-%s\n", section->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||||
|
MM_INDENT();
|
||||||
|
writer_put_str(tfc, "}\n\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mmc->indent_level--;
|
||||||
|
|
||||||
|
} else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
|
||||||
|
|
||||||
|
MM_INDENT();
|
||||||
|
writer_put_str(tfc, "end\n");
|
||||||
|
|
||||||
|
if (sec_data.section_id) {
|
||||||
|
MM_INDENT();
|
||||||
|
writer_put_str(tfc, "class ");
|
||||||
|
writer_put_str(tfc, sec_data.section_id);
|
||||||
|
writer_put_str(tfc, " ff-");
|
||||||
|
writer_put_str(tfc, section->name);
|
||||||
|
writer_put_str(tfc, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
mmc->indent_level--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
|
||||||
|
if (sec_data.src_id && sec_data.dest_id
|
||||||
|
&& !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
|
||||||
|
switch (mmc->diagram_config->diagram_type) {
|
||||||
|
case AV_DIAGRAMTYPE_GRAPH:
|
||||||
|
|
||||||
|
if (sec_data.section_type && mmc->enable_link_colors)
|
||||||
|
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);
|
||||||
|
else
|
||||||
|
av_bprintf(&mmc->link_buf, "\n %s ==", sec_data.src_id);
|
||||||
|
|
||||||
|
if (buf->len > 0) {
|
||||||
|
av_bprintf(&mmc->link_buf, " \"%s", buf->str);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
|
||||||
|
av_bprintf(&mmc->link_buf, "<br> ");
|
||||||
|
|
||||||
|
av_bprintf(&mmc->link_buf, "\" ==");
|
||||||
|
}
|
||||||
|
|
||||||
|
av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||||
|
|
||||||
|
|
||||||
|
av_bprintf(&mmc->link_buf, "\n %s", sec_data.src_id);
|
||||||
|
|
||||||
|
switch (sec_data.link_type) {
|
||||||
|
case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
|
||||||
|
av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
|
||||||
|
break;
|
||||||
|
case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
|
||||||
|
av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
|
||||||
|
break;
|
||||||
|
case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
|
||||||
|
av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
|
||||||
|
break;
|
||||||
|
case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
|
||||||
|
av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tfc->level == 0) {
|
||||||
|
|
||||||
|
writer_put_str(tfc, "\n");
|
||||||
|
if (mmc->create_html) {
|
||||||
|
char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
|
||||||
|
if (!token_pos) {
|
||||||
|
av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
token_pos += strlen("__###__");
|
||||||
|
writer_put_str(tfc, token_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tfc->level == 1) {
|
||||||
|
|
||||||
|
if (mmc->link_buf.len > 0) {
|
||||||
|
writer_put_str(tfc, mmc->link_buf.str);
|
||||||
|
av_bprint_clear(&mmc->link_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer_put_str(tfc, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
|
||||||
|
const char *str, int64_t num, const int is_int)
|
||||||
|
{
|
||||||
|
MermaidContext *mmc = tfc->priv;
|
||||||
|
const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
|
||||||
|
|
||||||
|
if (!section)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AVBPrint *buf = &tfc->section_pbuf[tfc->level];
|
||||||
|
struct section_data sec_data = mmc->section_data[tfc->level];
|
||||||
|
int exit = 0;
|
||||||
|
|
||||||
|
if (section->id_key && !strcmp(section->id_key, key)) {
|
||||||
|
mmc->section_data[tfc->level].section_id = av_strdup(str);
|
||||||
|
exit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
|
||||||
|
mmc->section_data[tfc->level].dest_id = av_strdup(str);
|
||||||
|
exit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section->src_id_key && !strcmp(section->src_id_key, key)) {
|
||||||
|
mmc->section_data[tfc->level].src_id = av_strdup(str);
|
||||||
|
exit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section->linktype_key && !strcmp(section->linktype_key, key)) {
|
||||||
|
mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;;
|
||||||
|
exit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (exit)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
|
||||||
|
|| (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
|
||||||
|
|
||||||
|
if (exit)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (mmc->diagram_config->diagram_type) {
|
||||||
|
case AV_DIAGRAMTYPE_GRAPH:
|
||||||
|
|
||||||
|
if (is_int) {
|
||||||
|
writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
|
||||||
|
} else {
|
||||||
|
////AVBPrint b;
|
||||||
|
////av_bprint_init(&b, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||||
|
const char *tmp = av_strireplace(str, "\"", "'");
|
||||||
|
////av_bprint_escape(&b, str, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_STRICT);
|
||||||
|
writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
|
||||||
|
av_freep(&tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||||
|
|
||||||
|
if (!is_int && str)
|
||||||
|
{
|
||||||
|
const char *col_type;
|
||||||
|
|
||||||
|
if (key[0] == '_')
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sec_data.section_id && !strcmp(str, sec_data.section_id))
|
||||||
|
col_type = "PK";
|
||||||
|
else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
|
||||||
|
col_type = "FK";
|
||||||
|
else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
|
||||||
|
col_type = "FK";
|
||||||
|
else
|
||||||
|
col_type = "";
|
||||||
|
|
||||||
|
MM_INDENT();
|
||||||
|
|
||||||
|
if (is_int)
|
||||||
|
writer_printf(tfc, " %s %"PRId64" %s\n", key, num, col_type);
|
||||||
|
else
|
||||||
|
writer_printf(tfc, " %s %s %s\n", key, str, col_type);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
|
||||||
|
|
||||||
|
if (exit)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (buf->len > 0)
|
||||||
|
av_bprintf(buf, "%s", "<br>");
|
||||||
|
|
||||||
|
av_bprintf(buf, "");
|
||||||
|
if (is_int)
|
||||||
|
av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
|
||||||
|
else
|
||||||
|
av_bprintf(buf, "<span>%s</span>", str);
|
||||||
|
|
||||||
|
mmc->nb_link_captions[tfc->level]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
|
||||||
|
{
|
||||||
|
mermaid_print_value(tfc, key, value, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
|
||||||
|
{
|
||||||
|
mermaid_print_value(tfc, key, NULL, value, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVTextFormatter avtextformatter_mermaid = {
|
||||||
|
.name = "mermaid",
|
||||||
|
.priv_size = sizeof(MermaidContext),
|
||||||
|
.init = mermaid_init,
|
||||||
|
.print_section_header = mermaid_print_section_header,
|
||||||
|
.print_section_footer = mermaid_print_section_footer,
|
||||||
|
.print_integer = mermaid_print_int,
|
||||||
|
.print_string = mermaid_print_str,
|
||||||
|
.flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
|
||||||
|
.priv_class = &mermaid_class,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const AVTextFormatter avtextformatter_mermaidhtml = {
|
||||||
|
.name = "mermaidhtml",
|
||||||
|
.priv_size = sizeof(MermaidContext),
|
||||||
|
.init = mermaid_init_html,
|
||||||
|
.print_section_header = mermaid_print_section_header,
|
||||||
|
.print_section_footer = mermaid_print_section_footer,
|
||||||
|
.print_integer = mermaid_print_int,
|
||||||
|
.print_string = mermaid_print_str,
|
||||||
|
.flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
|
||||||
|
.priv_class = &mermaid_class,
|
||||||
|
};
|
41
fftools/textformat/tf_mermaid.h
Normal file
41
fftools/textformat/tf_mermaid.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) The FFmpeg developers
|
||||||
|
*
|
||||||
|
* This file is part of FFmpeg.
|
||||||
|
*
|
||||||
|
* FFmpeg is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* FFmpeg is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with FFmpeg; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FFTOOLS_TEXTFORMAT_TF_MERMAID_H
|
||||||
|
#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
AV_DIAGRAMTYPE_GRAPH,
|
||||||
|
AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
|
||||||
|
} AVDiagramType;
|
||||||
|
|
||||||
|
typedef struct AVDiagramConfig {
|
||||||
|
AVDiagramType diagram_type;
|
||||||
|
const char *diagram_css;
|
||||||
|
const char *html_template;
|
||||||
|
} AVDiagramConfig;
|
||||||
|
|
||||||
|
|
||||||
|
void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
|
||||||
|
|
||||||
|
void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */
|
Reference in New Issue
Block a user