Skip to content

Commit 6f370ed

Browse files
committed
tools/ffmpeg-sg: Add show-graph wrapper script for Linux
Signed-off-by: softworkz <softworkz@hotmail.com>
1 parent 5fea5e3 commit 6f370ed

File tree

1 file changed

+249
-0
lines changed

1 file changed

+249
-0
lines changed

tools/ffmpeg-sg

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#!/bin/bash
2+
#
3+
# ffmpeg-sg - FFmpeg Show-Graph Wrapper (aka killer feature)
4+
# Show the FFmpeg execution graph in default browser
5+
#
6+
# Copyright (c) 2025 softworkz
7+
#
8+
# This file is part of FFmpeg.
9+
#
10+
# FFmpeg is free software; you can redistribute it and/or
11+
# modify it under the terms of the GNU Lesser General Public
12+
# License as published by the Free Software Foundation; either
13+
# version 2.1 of the License, or (at your option) any later version.
14+
#
15+
# FFmpeg is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18+
# Lesser General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU Lesser General Public
21+
# License along with FFmpeg; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23+
#
24+
25+
set -euo pipefail
26+
27+
# Check for privilege level
28+
check_privileges() {
29+
# Check if running as root (UID 0)
30+
if [ "$(id -u)" -eq 0 ]; then
31+
echo "Error: This script should not be run as root for security reasons." >&2
32+
echo "Media processing and browser launching do not require root privileges." >&2
33+
exit 1
34+
fi
35+
36+
# Check if running as sudo
37+
if [ -n "${SUDO_USER:-}" ] || [ -n "${SUDO_UID:-}" ]; then
38+
echo "Error: This script should not be run with sudo for security reasons." >&2
39+
echo "Please run as a regular user: ./ffmpeg-sg [options]" >&2
40+
exit 1
41+
fi
42+
43+
# Check other privilege indicators
44+
if [ -n "${PKEXEC_UID:-}" ]; then
45+
echo "Error: This script should not be run with elevated privileges (pkexec)." >&2
46+
exit 1
47+
fi
48+
}
49+
50+
# Validate path chars
51+
validate_path() {
52+
local path="$1"
53+
case "$path" in
54+
*[!ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.-]*)
55+
echo "Error: Invalid characters in path: $path" >&2
56+
return 1
57+
;;
58+
esac
59+
return 0
60+
}
61+
62+
# Get secure temp folder
63+
get_temp_dir() {
64+
local uid=$(id -u)
65+
local temp_bases=("/tmp" "/var/tmp")
66+
67+
for base in "${temp_bases[@]}"; do
68+
local temp_dir="$base/ffmpeg-$uid"
69+
70+
if ! validate_path "$temp_dir"; then
71+
continue
72+
fi
73+
74+
# Verify perms and owner
75+
if [ -d "$temp_dir" ]; then
76+
local perms=$(stat -c %a "$temp_dir" 2>/dev/null || echo "")
77+
if [ "$perms" != "700" ]; then
78+
echo "Error: Temp directory exists with unsafe permissions ($perms). Expected 700." >&2
79+
exit 1
80+
fi
81+
82+
local owner=$(stat -c %u "$temp_dir" 2>/dev/null || echo "")
83+
if [ "$owner" != "$uid" ]; then
84+
echo "Error: Temp directory not owned by current user." >&2
85+
exit 1
86+
fi
87+
else
88+
# Create folder
89+
if ! mkdir -m 700 "$temp_dir" 2>/dev/null; then
90+
continue
91+
fi
92+
fi
93+
94+
echo "$temp_dir"
95+
return 0
96+
done
97+
98+
echo "Error: Unable to determine temp directory." >&2
99+
exit 1
100+
}
101+
102+
# Create HTML filename and pre-create file
103+
create_unique_html_file() {
104+
local temp_dir="$1"
105+
local base_timestamp=$(date '+%Y-%m-%d_%H-%M-%S')
106+
local base_milliseconds=$(date '+%3N')
107+
108+
local filename="ffmpeg_graph_${base_timestamp}_${base_milliseconds}.html"
109+
local full_path="$temp_dir/$filename"
110+
111+
if ! validate_path "$full_path"; then
112+
echo "Error: Generated invalid file path." >&2
113+
exit 1
114+
fi
115+
116+
if (set -C; echo "<!-- FFmpeg Graph Placeholder -->" > "$full_path") 2>/dev/null; then
117+
echo "$full_path"
118+
return 0
119+
fi
120+
121+
echo "Error: Could not create unique HTML file." >&2
122+
exit 1
123+
}
124+
125+
# Check for xdg-open
126+
check_xdg_open() {
127+
# Accept only standard system locations - no PATH lookup
128+
local xdg_locations=("/usr/bin/xdg-open" "/bin/xdg-open" "/usr/local/bin/xdg-open")
129+
130+
for location in "${xdg_locations[@]}"; do
131+
if [ -x "$location" ]; then
132+
echo "$location"
133+
return 0
134+
fi
135+
done
136+
137+
echo "Error: xdg-open not found in standard system locations." >&2
138+
echo "Checked: ${xdg_locations[*]}" >&2
139+
return 1
140+
}
141+
142+
# Launch browser
143+
open_html_in_browser() {
144+
local html_path="$1"
145+
146+
if [ ! -f "$html_path" ]; then
147+
echo "Warning: HTML file not found: $html_path" >&2
148+
return 1
149+
fi
150+
151+
# Validate again
152+
if ! validate_path "$html_path"; then
153+
echo "Error: Invalid file path for browser: $html_path" >&2
154+
return 1
155+
fi
156+
157+
local xdg_open_path
158+
xdg_open_path=$(check_xdg_open)
159+
if [ $? -ne 0 ]; then
160+
return 1
161+
fi
162+
163+
# Launch browser
164+
"$xdg_open_path" "$html_path" </dev/null >/dev/null 2>&1 &
165+
166+
if [ $? -eq 0 ]; then
167+
echo "Execution graph opened in browser: $html_path" >&2
168+
return 0
169+
else
170+
echo "Warning: Could not open '$html_path' in a browser." >&2
171+
return 1
172+
fi
173+
}
174+
175+
# Check for conflicting parameters
176+
check_conflicting_params() {
177+
local args=("$@")
178+
179+
for arg in "${args[@]}"; do
180+
case "$arg" in
181+
-print_graphs_file)
182+
echo "Error: -print_graphs_file parameter already provided. This script manages graph file generation automatically." >&2
183+
exit 1
184+
;;
185+
-print_graphs_format)
186+
echo "Error: -print_graphs_format parameter already provided. This script uses mermaidhtml format automatically." >&2
187+
exit 1
188+
;;
189+
esac
190+
done
191+
}
192+
193+
# Cleanup temp file on signal
194+
cleanup_on_signal() {
195+
local html_file="$1"
196+
if [ -f "$html_file" ]; then
197+
rm -f "$html_file" 2>/dev/null || true
198+
fi
199+
exit 130 # 128 + SIGINT
200+
}
201+
202+
main() {
203+
check_privileges
204+
205+
# Check if ffmpeg exists in current dir
206+
if [ ! -x "./ffmpeg" ]; then
207+
echo "Error: ./ffmpeg not found or not executable in current directory." >&2
208+
exit 1
209+
fi
210+
211+
# Check params
212+
check_conflicting_params "$@"
213+
214+
local temp_dir
215+
temp_dir=$(get_temp_dir)
216+
217+
local html_file
218+
html_file=$(create_unique_html_file "$temp_dir")
219+
220+
trap "cleanup_on_signal '$html_file'" INT TERM
221+
222+
# Set umask for file creation
223+
umask 077
224+
225+
# Execute ffmpeg with graph printing options and all passed arguments
226+
local ffmpeg_exit_code=0
227+
./ffmpeg -print_graphs_file "$html_file" -print_graphs_format mermaidhtml "$@" || ffmpeg_exit_code=$?
228+
229+
trap - INT TERM
230+
231+
# Open browser
232+
if [ -f "$html_file" ]; then
233+
local file_size=$(stat -c%s "$html_file" 2>/dev/null || echo 0)
234+
local placeholder_size=34
235+
236+
if [ "$file_size" -gt "$placeholder_size" ]; then
237+
open_html_in_browser "$html_file"
238+
else
239+
echo "Warning: FFmpeg completed but no graph data was written." >&2
240+
fi
241+
else
242+
echo "Warning: FFmpeg completed but no graph file was found." >&2
243+
fi
244+
245+
# Exit with ffmpeg exit code
246+
exit $ffmpeg_exit_code
247+
}
248+
249+
main "$@"

0 commit comments

Comments
 (0)