From: softworkz <softwo...@hotmail.com> remember this: -sg <= show-graph
Signed-off-by: softworkz <softwo...@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 | 205 +++++++++++++++++++++++++++++++++++ fftools/graph/graphprint.c | 48 +++++++- fftools/graph/graphprint.h | 32 ++++++ 9 files changed, 296 insertions(+), 3 deletions(-) create mode 100644 fftools/graph/filelauncher.c diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi index 35675b5309..6e9e7aed0e 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. +@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 361a4fd574..56a2910212 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 6766ec209c..9875a1f7fd 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) + if (print_graphs || print_graphs_file || show_graph) 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..0cf5f15cf1 --- /dev/null +++ b/fftools/graph/filelauncher.c @@ -0,0 +1,205 @@ +/* + * 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 <stdlib.h> +#include <string.h> + +#if defined(_WIN32) +# include <windows.h> +# include <shellapi.h> +#else +# include <sys/time.h> +# include <time.h> +#endif +#include "graphprint.h" + +int ff_open_html_in_browser(const char *html_path) +{ + if (!html_path || !*html_path) + return -1; + +#if defined(_WIN32) + + // --- Windows --------------------------------- + { + 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__) + + // --- macOS ----------------------------------- + { + // "open" is the macOS command to open a file/URL with the default application + char cmd[1024]; + snprintf(cmd, sizeof(cmd), "open '%s' 1>/dev/null 2>&1 &", html_path); + if (system(cmd) != 0) + return -1; + return 0; + } + +#else + + // --- Linux / Unix-like ----------------------- + // We'll try xdg-open, then gnome-open, then kfmclient + { + // Helper macro to try one browser command + // Returns 0 on success, -1 on failure + #define TRY_CMD(prog) do { \ + char buf[1024]; \ + snprintf(buf, sizeof(buf), "%s '%s' 1>/dev/null 2>&1 &", \ + (prog), html_path); \ + int ret = system(buf); \ + /* On Unix: system() returns -1 if the shell can't run. */\ + /* Otherwise, check exit code in lower 8 bits. */\ + if (ret != -1 && WIFEXITED(ret) && WEXITSTATUS(ret) == 0) \ + return 0; \ + } while (0) + + TRY_CMD("xdg-open"); + TRY_CMD("gnome-open"); + TRY_CMD("kfmclient exec"); + + fprintf(stderr, "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 + + // --- macOS / Linux / Unix ----------------------- + // Follow typical POSIX convention: check common env variables + // and fallback to /tmp if not found. + { + const char *tmp = getenv("TMPDIR"); + if (!tmp || !*tmp) tmp = getenv("TMP"); + if (!tmp || !*tmp) tmp = getenv("TEMP"); + if (!tmp || !*tmp) tmp = "/tmp"; + + // Copy into buf, ensure there's a trailing slash + size_t len = strlen(tmp); + if (len + 2 > size) { + // Need up to len + 1 for slash + 1 for null terminator + return -1; + } + + strcpy(buf, tmp); + // Append slash if necessary + if (buf[len - 1] != '/' && buf[len - 1] != '\\') { +#if defined(__APPLE__) + // On macOS/Unix, use forward slash + buf[len] = '/'; + buf[len + 1] = '\0'; +#else + // Technically on Unix it's always '/', but here's how you'd do if needed: + buf[len] = '/'; + buf[len + 1] = '\0'; +#endif + } + return 0; + } + +#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 05c06f80fb..635bae6d5e 100644 --- a/fftools/graph/graphprint.c +++ b/fftools/graph/graphprint.c @@ -873,6 +873,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) { @@ -1097,5 +1102,46 @@ cleanup: int print_filtergraphs(FilterGraph **graphs, int nb_graphs, InputFile **ifiles, int nb_ifiles, OutputFile **ofiles, int nb_ofiles) { - return 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"); + return ret; + } + + 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"); + return ret; + } + + 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); + } + } + + 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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".