mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2025-05-16 22:48:29 +08:00
fftools/graphprint: Now, make it a Killer-Feature!
remember this: -sg <= means Show Graph Signed-off-by: softworkz <softworkz@hotmail.com>
This commit is contained in:
@ -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}.
|
||||
|
||||
|
@ -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 \
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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" },
|
||||
|
205
fftools/graph/filelauncher.c
Normal file
205
fftools/graph/filelauncher.c
Normal file
@ -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
|
||||
}
|
@ -879,6 +879,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) {
|
||||
@ -1103,5 +1108,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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
Reference in New Issue
Block a user