[FFmpeg-devel] [PATCH 1/2] [RFC-NotForMerge] fftools/graphprint: The "bad" patch
softworkz
ffmpegagent at gmail.com
Wed May 28 18:24:08 EEST 2025
From: softworkz <softworkz at hotmail.com>
---
doc/ffmpeg.texi | 4 +
fftools/Makefile | 1 +
fftools/ffmpeg.c | 2 +-
fftools/ffmpeg.h | 1 +
fftools/ffmpeg_filter.c | 2 +-
fftools/ffmpeg_opt.c | 4 +
fftools/graph/filelauncher.c | 200 +++++++++++++++++++++++++++++++++++
fftools/graph/graphprint.c | 48 ++++++++-
fftools/graph/graphprint.h | 32 ++++++
9 files changed, 291 insertions(+), 3 deletions(-)
create mode 100644 fftools/graph/filelauncher.c
diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi
index 35675b5309..4bcb6d6a01 100644
--- a/doc/ffmpeg.texi
+++ b/doc/ffmpeg.texi
@@ -1404,6 +1404,10 @@ Writes execution graph details to the specified file in the format set via -prin
Sets the output format (available formats are: default, compact, csv, flat, ini, json, xml, mermaid, mermaidhtml)
The default format is json.
+ at item -sg (@emph{global})
+Writes the execution graph to a temporary html file (mermaidhtml format) and
+tries to launch it in the default browser.
+
@item -progress @var{url} (@emph{global})
Send program-friendly progress information to @var{url}.
diff --git a/fftools/Makefile b/fftools/Makefile
index c1eba733da..f6492cb1d7 100644
--- a/fftools/Makefile
+++ b/fftools/Makefile
@@ -22,6 +22,7 @@ OBJS-ffmpeg += \
fftools/ffmpeg_opt.o \
fftools/ffmpeg_sched.o \
fftools/graph/graphprint.o \
+ fftools/graph/filelauncher.o \
fftools/sync_queue.o \
fftools/thread_queue.o \
fftools/textformat/avtextformat.o \
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index de607cac93..0a7675fee0 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -309,7 +309,7 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
static void ffmpeg_cleanup(int ret)
{
- if ((print_graphs || print_graphs_file) && nb_output_files > 0)
+ if ((print_graphs || print_graphs_file || show_graph) && nb_output_files > 0)
print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
if (do_benchmark) {
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7fbf0ad532..49fea0307d 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -721,6 +721,7 @@ extern int print_graphs;
extern char *print_graphs_file;
extern char *print_graphs_format;
extern int auto_conversion_filters;
+extern int show_graph;
extern const AVIOInterruptCB int_cb;
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index b774606562..e82e333b7f 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -2985,7 +2985,7 @@ read_frames:
finish:
- if (print_graphs || print_graphs_file)
+ if (print_graphs || print_graphs_file || show_graph)
print_filtergraph(fg, fgt.graph);
// EOF is normal termination
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 3d1efe32f9..24713d640f 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -79,6 +79,7 @@ int vstats_version = 2;
int print_graphs = 0;
char *print_graphs_file = NULL;
char *print_graphs_format = NULL;
+int show_graph = 0;
int auto_conversion_filters = 1;
int64_t stats_period = 500000;
@@ -1748,6 +1749,9 @@ const OptionDef options[] = {
{ "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" },
+ { "sg", OPT_TYPE_BOOL, 0,
+ { &show_graph },
+ "create execution graph as temporary html file and try to launch it in the default browser" },
{ "auto_conversion_filters", OPT_TYPE_BOOL, OPT_EXPERT,
{ &auto_conversion_filters },
"enable automatic conversion filters globally" },
diff --git a/fftools/graph/filelauncher.c b/fftools/graph/filelauncher.c
new file mode 100644
index 0000000000..b46967e334
--- /dev/null
+++ b/fftools/graph/filelauncher.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 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
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#if defined(_WIN32)
+# include <windows.h>
+# include <shellapi.h>
+#else
+# include <sys/time.h>
+# include <time.h>
+# include <errno.h>
+# include <sys/stat.h>
+# include <sys/types.h>
+# include <unistd.h>
+#endif
+#include "graphprint.h"
+#include "libavutil/bprint.h"
+
+int ff_open_html_in_browser(const char *html_path)
+{
+ if (!html_path || !*html_path)
+ return -1;
+
+#if defined(_WIN32)
+
+ {
+ HINSTANCE rc = ShellExecuteA(NULL, "open", html_path, NULL, NULL, SW_SHOWNORMAL);
+ if ((UINT_PTR)rc <= 32) {
+ // Fallback: system("start ...")
+ char cmd[1024];
+ _snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "start \"\" \"%s\"", html_path);
+ if (system(cmd) != 0)
+ return -1;
+ }
+ return 0;
+ }
+
+#elif defined(__APPLE__)
+
+ {
+ av_log(NULL, AV_LOG_WARNING, "Browser launch not implemented...\n");
+ return 0;
+ }
+
+#else
+
+ // --- Linux / Unix-like -----------------------
+ {
+ static const char safe_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/-._";
+ AVBPrint buf;
+
+ // Due to the way how the temp path and file name are constructed, this check is not
+ // actually required and just for illustration of which chars can even occur in the path.
+ for (const char *p = html_path; *p; ++p) {
+ if (strchr(safe_chars, (unsigned char)*p) == NULL) {
+ av_log(NULL, AV_LOG_ERROR, "Invalid file path: '%s'.\n", html_path);
+ return -1;
+ }
+ }
+
+ av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+ av_bprintf(&buf, "xdg-open '%s' </dev/null 1>/dev/null 2>&1 &", html_path);
+
+ int ret = system(buf.str);
+
+ if (ret != -1 && WIFEXITED(ret) && WEXITSTATUS(ret) == 0)
+ return 0;
+
+ av_log(NULL, AV_LOG_WARNING, "Could not open '%s' in a browser.\n", html_path);
+ return -1;
+ }
+
+#endif
+}
+
+
+int ff_get_temp_dir(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+ // --- Windows ------------------------------------
+ {
+ // GetTempPathA returns length of the string (including trailing backslash).
+ // If the return value is greater than buffer size, it's an error.
+ DWORD len = GetTempPathA((DWORD)size, buf);
+ if (len == 0 || len > size) {
+ // Could not retrieve or buffer is too small
+ return -1;
+ }
+ return 0;
+ }
+
+#else
+
+ static const char *bases[] = { "/tmp", "/var/tmp", NULL };
+ AVBPrint bp;
+
+ av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+ uid_t uid = getuid();
+
+ for (int i = 0; bases[i]; i++) {
+ av_bprint_clear(&bp);
+ av_bprintf(&bp, "%s/ffmpeg-%u", bases[i], uid);
+
+ if (mkdir(bp.str, 0700) == -1 && errno != EEXIST)
+ continue;
+
+ av_bprint_chars(&bp, '/', 1);
+
+ if (bp.len > size - 1)
+ return -1;
+
+ snprintf(buf, size, "%s", bp.str);
+ return 0;
+ }
+
+ av_log(NULL, AV_LOG_ERROR, "Unable to determine temp directory.\n");
+ av_bprint_clear(&bp);
+ return -1;
+
+#endif
+}
+
+int ff_make_timestamped_html_name(char *buf, size_t size)
+{
+#if defined(_WIN32)
+
+ /*----------- Windows version -----------*/
+ SYSTEMTIME st;
+ GetLocalTime(&st);
+ /*
+ st.wYear, st.wMonth, st.wDay,
+ st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
+ */
+ int written = _snprintf_s(buf, size, _TRUNCATE,
+ "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+ st.wYear,
+ st.wMonth,
+ st.wDay,
+ st.wHour,
+ st.wMinute,
+ st.wSecond,
+ st.wMilliseconds);
+ if (written < 0)
+ return -1; /* Could not write into buffer */
+ return 0;
+
+#else
+
+ /*----------- macOS / Linux / Unix version -----------*/
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL) != 0) {
+ return -1; /* gettimeofday failed */
+ }
+
+ struct tm local_tm;
+ localtime_r(&tv.tv_sec, &local_tm);
+
+ int ms = (int)(tv.tv_usec / 1000); /* convert microseconds to milliseconds */
+
+ /*
+ local_tm.tm_year is years since 1900,
+ local_tm.tm_mon is 0-based (0=Jan, 11=Dec)
+ */
+ int written = snprintf(buf, size,
+ "ffmpeg_graph_%04d-%02d-%02d_%02d-%02d-%02d_%03d.html",
+ local_tm.tm_year + 1900,
+ local_tm.tm_mon + 1,
+ local_tm.tm_mday,
+ local_tm.tm_hour,
+ local_tm.tm_min,
+ local_tm.tm_sec,
+ ms);
+ if (written < 0 || (size_t)written >= size) {
+ return -1; /* Buffer too small or formatting error */
+ }
+ return 0;
+
+#endif
+}
diff --git a/fftools/graph/graphprint.c b/fftools/graph/graphprint.c
index 852a8f6c0c..346e139494 100644
--- a/fftools/graph/graphprint.c
+++ b/fftools/graph/graphprint.c
@@ -884,6 +884,11 @@ static int init_graphprint(GraphPrintContext **pgpc, AVBPrint *target_buf)
av_bprint_init(target_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+ if (show_graph) {
+ if (!print_graphs_format || strcmp(print_graphs_format, "mermaidhtml") != 0)
+ print_graphs_format = av_strdup("mermaidhtml");
+ }
+
if (!print_graphs_format)
print_graphs_format = av_strdup("json");
if (!print_graphs_format) {
@@ -1108,7 +1113,48 @@ cleanup:
int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles)
{
- int ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+ int ret;
+
+ if (show_graph) {
+ char buf[2048];
+ AVBPrint bp;
+
+ av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ print_graphs = 0;
+
+ ret = ff_get_temp_dir(buf, sizeof(buf));
+ if (ret) {
+ av_log(NULL, AV_LOG_ERROR, "Error getting temp directory path for graph output file\n");
+ goto fail;
+ }
+
+ av_bprint_append_data(&bp, buf, strlen(buf));
+
+ ret = ff_make_timestamped_html_name(buf, sizeof(buf));
+ if (ret) {
+ av_log(NULL, AV_LOG_ERROR, "Error creating temp file name for graph output file\n");
+ goto fail;
+ }
+
+ av_bprint_append_data(&bp, buf, strlen(buf));
+
+ av_bprint_finalize(&bp, &print_graphs_file);
+ }
+
+ ret = print_filtergraphs_priv(graphs, nb_graphs, ifiles, nb_ifiles, ofiles, nb_ofiles);
+
+ if (!ret && show_graph) {
+ av_log(NULL, AV_LOG_INFO, "Execution graph saved as: %s\n", print_graphs_file);
+ av_log(NULL, AV_LOG_INFO, "Trying to launch graph in browser...\n");
+
+ ret = ff_open_html_in_browser(print_graphs_file);
+ if (ret) {
+ av_log(NULL, AV_LOG_ERROR, "Browser could not be launched for execution graph display\nPlease open manually: %s\n", print_graphs_file);
+ }
+ }
+
+fail:
ff_resman_uninit();
return ret;
}
diff --git a/fftools/graph/graphprint.h b/fftools/graph/graphprint.h
index 9f043cc273..43f769870b 100644
--- a/fftools/graph/graphprint.h
+++ b/fftools/graph/graphprint.h
@@ -27,4 +27,36 @@ int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles,
int print_filtergraph(FilterGraph *fg, AVFilterGraph *graph);
+/**
+ * Open an HTML file in the default browser (Windows, macOS, Linux/Unix).
+ *
+ * @param html_path Absolute or relative path to the HTML file.
+ * @return 0 on success, -1 on failure.
+ *
+ * NOTE: This uses system() calls for non-Windows, and ShellExecute on Windows.
+ * Exercise caution if 'html_path' is untrusted (possible command injection).
+ */
+int ff_open_html_in_browser(const char *html_path);
+
+/**
+ * Retrieve the system's temporary directory.
+ *
+ * @param buf Output buffer to store the temp directory path (including trailing slash)
+ * @param size Size of the output buffer in bytes
+ * @return 0 on success, -1 on failure (buffer too small or other errors)
+ *
+ * Note: On most platforms, the path will include a trailing slash (e.g. "C:\\Users\\...\\Temp\\" on Windows, "/tmp/" on Unix).
+ */
+int ff_get_temp_dir(char *buf, size_t size);
+
+/**
+ * Create a timestamped HTML filename, e.g.:
+ * ffmpeg_graph_2024-01-01_22-12-59_123.html
+ *
+ * @param buf Pointer to buffer where the result is stored
+ * @param size Size of the buffer in bytes
+ * @return 0 on success, -1 on error (e.g. buffer too small)
+ */
+int ff_make_timestamped_html_name(char *buf, size_t size);
+
#endif /* FFTOOLS_GRAPH_GRAPHPRINT_H */
--
ffmpeg-codebot
More information about the ffmpeg-devel
mailing list