diff --git a/.gitignore b/.gitignore index 59c89da5e03b5..989c702b6ca25 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ /.config /.version /ffmpeg +/ffmpeg-sg /ffplay /ffprobe /config.asm diff --git a/fftools/Makefile b/fftools/Makefile index b3c08ae5a0e8b..378de7966509e 100644 --- a/fftools/Makefile +++ b/fftools/Makefile @@ -5,6 +5,11 @@ AVPROGS-$(CONFIG_FFPROBE) += ffprobe AVPROGS := $(AVPROGS-yes:%=%$(PROGSSUF)$(EXESUF)) PROGS += $(AVPROGS) +FFMPEG_SG_SCRIPT = $(SRC_PATH)/tools/ffmpeg-sg$(if $(filter .exe,$(EXESUF)),.cmd) +FFMPEG_SG_TARGET = ffmpeg-sg$(EXESUF) +FFMPEG_SG_ENABLED = $(and $(filter $(CONFIG_FFMPEG),yes),$(or $(filter $(target_os),win32),$(if $(target_os),,yes))) +FFMPEG_SG_FILES = $(if $(FFMPEG_SG_ENABLED),$(FFMPEG_SG_TARGET)) + AVBASENAMES = ffmpeg ffplay ffprobe ALLAVPROGS = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF)) ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF)) @@ -67,7 +72,10 @@ endef $(foreach P,$(AVPROGS-yes),$(eval $(call DOFFTOOL,$(P)))) -all: $(AVPROGS) +all: $(AVPROGS) $(FFMPEG_SG_FILES) + +$(FFMPEG_SG_TARGET): $(FFMPEG_SG_SCRIPT) + $(Q)$(CP) -p $(FFMPEG_SG_SCRIPT) $(FFMPEG_SG_TARGET) fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools OUTDIRS += fftools @@ -85,11 +93,13 @@ install-progs-$(CONFIG_SHARED): install-libs install-progs: install-progs-yes $(AVPROGS) $(Q)mkdir -p "$(BINDIR)" $(INSTALL) -c -m 755 $(AVPROGS) "$(BINDIR)" + $(if $(FFMPEG_SG_FILES),$(INSTALL) -c -m 755 $(FFMPEG_SG_FILES) "$(BINDIR)") uninstall: uninstall-progs uninstall-progs: $(RM) $(addprefix "$(BINDIR)/", $(ALLAVPROGS)) + $(if $(FFMPEG_SG_FILES),$(RM) "$(BINDIR)/$(FFMPEG_SG_FILES)") clean:: - $(RM) $(ALLAVPROGS) $(ALLAVPROGS_G) $(CLEANSUFFIXES:%=fftools/%) $(CLEANSUFFIXES:%=fftools/graph/%) $(CLEANSUFFIXES:%=fftools/textformat/%) + $(RM) $(ALLAVPROGS) $(ALLAVPROGS_G) $(CLEANSUFFIXES:%=fftools/%) $(CLEANSUFFIXES:%=fftools/graph/%) $(CLEANSUFFIXES:%=fftools/textformat/%) $(FFMPEG_SG_FILES) diff --git a/tools/ffmpeg-sg b/tools/ffmpeg-sg new file mode 100755 index 0000000000000..c8c298f8e0285 --- /dev/null +++ b/tools/ffmpeg-sg @@ -0,0 +1,249 @@ +#!/bin/bash +# +# ffmpeg-sg - FFmpeg Show-Graph Wrapper (aka killer feature) +# Show the FFmpeg execution graph in default browser +# +# 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 +# + +set -euo pipefail + +# Check for privilege level +check_privileges() { + # Check if running as root (UID 0) + if [ "$(id -u)" -eq 0 ]; then + echo "Error: This script should not be run as root for security reasons." >&2 + echo "Media processing and browser launching do not require root privileges." >&2 + exit 1 + fi + + # Check if running as sudo + if [ -n "${SUDO_USER:-}" ] || [ -n "${SUDO_UID:-}" ]; then + echo "Error: This script should not be run with sudo for security reasons." >&2 + echo "Please run as a regular user: ./ffmpeg-sg [options]" >&2 + exit 1 + fi + + # Check other privilege indicators + if [ -n "${PKEXEC_UID:-}" ]; then + echo "Error: This script should not be run with elevated privileges (pkexec)." >&2 + exit 1 + fi +} + +# Validate path chars +validate_path() { + local path="$1" + case "$path" in + *[!ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.-]*) + echo "Error: Invalid characters in path: $path" >&2 + return 1 + ;; + esac + return 0 +} + +# Get secure temp folder +get_temp_dir() { + local uid=$(id -u) + local temp_bases=("/tmp" "/var/tmp") + + for base in "${temp_bases[@]}"; do + local temp_dir="$base/ffmpeg-$uid" + + if ! validate_path "$temp_dir"; then + continue + fi + + # Verify perms and owner + if [ -d "$temp_dir" ]; then + local perms=$(stat -c %a "$temp_dir" 2>/dev/null || echo "") + if [ "$perms" != "700" ]; then + echo "Error: Temp directory exists with unsafe permissions ($perms). Expected 700." >&2 + exit 1 + fi + + local owner=$(stat -c %u "$temp_dir" 2>/dev/null || echo "") + if [ "$owner" != "$uid" ]; then + echo "Error: Temp directory not owned by current user." >&2 + exit 1 + fi + else + # Create folder + if ! mkdir -m 700 "$temp_dir" 2>/dev/null; then + continue + fi + fi + + echo "$temp_dir" + return 0 + done + + echo "Error: Unable to determine temp directory." >&2 + exit 1 +} + +# Create HTML filename and pre-create file +create_unique_html_file() { + local temp_dir="$1" + local base_timestamp=$(date '+%Y-%m-%d_%H-%M-%S') + local base_milliseconds=$(date '+%3N') + + local filename="ffmpeg_graph_${base_timestamp}_${base_milliseconds}.html" + local full_path="$temp_dir/$filename" + + if ! validate_path "$full_path"; then + echo "Error: Generated invalid file path." >&2 + exit 1 + fi + + if (set -C; echo "" > "$full_path") 2>/dev/null; then + echo "$full_path" + return 0 + fi + + echo "Error: Could not create unique HTML file." >&2 + exit 1 +} + +# Check for xdg-open +check_xdg_open() { + # Accept only standard system locations - no PATH lookup + local xdg_locations=("/usr/bin/xdg-open" "/bin/xdg-open" "/usr/local/bin/xdg-open") + + for location in "${xdg_locations[@]}"; do + if [ -x "$location" ]; then + echo "$location" + return 0 + fi + done + + echo "Error: xdg-open not found in standard system locations." >&2 + echo "Checked: ${xdg_locations[*]}" >&2 + return 1 +} + +# Launch browser +open_html_in_browser() { + local html_path="$1" + + if [ ! -f "$html_path" ]; then + echo "Warning: HTML file not found: $html_path" >&2 + return 1 + fi + + # Validate again + if ! validate_path "$html_path"; then + echo "Error: Invalid file path for browser: $html_path" >&2 + return 1 + fi + + local xdg_open_path + xdg_open_path=$(check_xdg_open) + if [ $? -ne 0 ]; then + return 1 + fi + + # Launch browser + "$xdg_open_path" "$html_path" /dev/null 2>&1 & + + if [ $? -eq 0 ]; then + echo "Execution graph opened in browser: $html_path" >&2 + return 0 + else + echo "Warning: Could not open '$html_path' in a browser." >&2 + return 1 + fi +} + +# Check for conflicting parameters +check_conflicting_params() { + local args=("$@") + + for arg in "${args[@]}"; do + case "$arg" in + -print_graphs_file) + echo "Error: -print_graphs_file parameter already provided. This script manages graph file generation automatically." >&2 + exit 1 + ;; + -print_graphs_format) + echo "Error: -print_graphs_format parameter already provided. This script uses mermaidhtml format automatically." >&2 + exit 1 + ;; + esac + done +} + +# Cleanup temp file on signal +cleanup_on_signal() { + local html_file="$1" + if [ -f "$html_file" ]; then + rm -f "$html_file" 2>/dev/null || true + fi + exit 130 # 128 + SIGINT +} + +main() { + check_privileges + + # Check if ffmpeg exists in current dir + if [ ! -x "./ffmpeg" ]; then + echo "Error: ./ffmpeg not found or not executable in current directory." >&2 + exit 1 + fi + + # Check params + check_conflicting_params "$@" + + local temp_dir + temp_dir=$(get_temp_dir) + + local html_file + html_file=$(create_unique_html_file "$temp_dir") + + trap "cleanup_on_signal '$html_file'" INT TERM + + # Set umask for file creation + umask 077 + + # Execute ffmpeg with graph printing options and all passed arguments + local ffmpeg_exit_code=0 + ./ffmpeg -print_graphs_file "$html_file" -print_graphs_format mermaidhtml "$@" || ffmpeg_exit_code=$? + + trap - INT TERM + + # Open browser + if [ -f "$html_file" ]; then + local file_size=$(stat -c%s "$html_file" 2>/dev/null || echo 0) + local placeholder_size=34 + + if [ "$file_size" -gt "$placeholder_size" ]; then + open_html_in_browser "$html_file" + else + echo "Warning: FFmpeg completed but no graph data was written." >&2 + fi + else + echo "Warning: FFmpeg completed but no graph file was found." >&2 + fi + + # Exit with ffmpeg exit code + exit $ffmpeg_exit_code +} + +main "$@" diff --git a/tools/ffmpeg-sg.cmd b/tools/ffmpeg-sg.cmd new file mode 100644 index 0000000000000..1268ac05c92ea --- /dev/null +++ b/tools/ffmpeg-sg.cmd @@ -0,0 +1,73 @@ +@echo off +setlocal EnableDelayedExpansion +REM +REM ffmpeg-sg - FFmpeg Show-Graph Wrapper (aka killer feature) +REM Show the FFmpeg execution graph in default browser +REM +REM Copyright (c) 2025 softworkz +REM +REM This file is part of FFmpeg. +REM +REM FFmpeg is free software; you can redistribute it and/or +REM modify it under the terms of the GNU Lesser General Public +REM License as published by the Free Software Foundation; either +REM version 2.1 of the License, or (at your option) any later version. +REM +REM FFmpeg is distributed in the hope that it will be useful, +REM but WITHOUT ANY WARRANTY; without even the implied warranty of +REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +REM Lesser General Public License for more details. +REM +REM You should have received a copy of the GNU Lesser General Public +REM License along with FFmpeg; if not, write to the Free Software +REM Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +REM + +REM Check for ffmpeg.exe in folder +if not exist "ffmpeg.exe" ( + echo Error: ffmpeg.exe not found in current directory. + exit /b 1 +) + +REM Check params +set "conflict_found=" +for %%i in (%*) do ( + if /i "%%i"=="-print_graphs_file" set "conflict_found=1" + if /i "%%i"=="-print_graphs_format" set "conflict_found=1" +) + +if defined conflict_found ( + echo Error: -print_graphs_file or -print_graphs_format parameter already provided. + echo This script manages graph file generation automatically. + exit /b 1 +) + +REM Validate temp dir +if not exist "%TEMP%" ( + echo Error: Temp directory not accessible + exit /b 1 +) + +REM Generate HTML filename +set "date_part=%date:~-4,4%-%date:~-10,2%-%date:~-7,2%" +set "time_part=%time:~0,2%-%time:~3,2%-%time:~6,2%" +set "date_part=%date_part:/=-%" +set "time_part=%time_part: =0%" + +set "html_file=%TEMP%\ffmpeg_graph_%date_part%_%time_part%_%RANDOM%.html" + +REM Execute ffmpeg +REM Use start /wait /b to avoid "Terminate batch job" prompt on Ctrl-C +start /wait /b ffmpeg.exe -print_graphs_file "%html_file%" -print_graphs_format mermaidhtml %* +set "ffmpeg_exit_code=%ERRORLEVEL%" + +REM Open browser if HTML file was created +if exist "%html_file%" ( + echo "Execution graph opened in browser: %html_file% + start "FFmpeg Graph" "%html_file%" +) else ( + echo Warning: FFmpeg completed but no graph file was generated. +) + +REM Exit with ffmpeg exit code +exit /b %ffmpeg_exit_code%