mirror of
https://github.com/winneon/gsu.git
synced 2026-09-10 07:16:25 -04:00
586 lines
13 KiB
Bash
Executable file
586 lines
13 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
VERSION="1.1.2-git"
|
|
config_path="${XDG_CONFIG_HOME:-${HOME}/.config}/gsu"
|
|
|
|
notify() {
|
|
echo -e "$1"
|
|
|
|
if [[ ! -z "$notify_arg" ]] && type -p notify-send >/dev/null; then
|
|
notify-send "gsu" "$1"
|
|
fi
|
|
}
|
|
|
|
print_help() {
|
|
echo "\
|
|
Usage: gsu [OPTION]... SOURCE
|
|
A general screenshot and upload utility for images, video, and gifs.
|
|
|
|
SOURCE defaults to \$XDG_CONFIG_HOME/gsu/imgs if nothing is provided.
|
|
The most common location for \$XDG_CONFIG_HOME is ~/.config.
|
|
|
|
GENERAL OPTIONS
|
|
-h, --help Show the help menu
|
|
-v, --version Show the current version
|
|
-l, --list-displays List your current displays and their resolutions.
|
|
--notify Send a libnotify notification of the output.
|
|
|
|
-u, --upload Upload after running the utility
|
|
-n, --nocursor Hide the cursor
|
|
-d, --display NUM|TYPE Set the selection to a specific display.
|
|
Can read from an argument or from stdin.
|
|
Values: number of display, 'active', 'all' (default)
|
|
|
|
-r, --dmenu Select the below action from dmenu or rofi.
|
|
-s, --screenshot Take a screenshot
|
|
-m, --video Record a video of the screen
|
|
-g, --gif Record a video and convert it to the gif format
|
|
|
|
VIDEO OPTIONS
|
|
-c, --countdown NUM Countdown before recording"
|
|
|
|
exit 0
|
|
}
|
|
|
|
print_version() {
|
|
echo "gsu $VERSION"
|
|
exit 0
|
|
}
|
|
|
|
print_displays() {
|
|
local monitors=$(xrandr | grep -o "[0-9]*x[0-9]*[+-][0-9]*[+-][0-9]*")
|
|
|
|
if [[ -z "$monitors" ]]; then
|
|
echo "gsu: unable to find any displays"
|
|
exit 1
|
|
fi
|
|
|
|
readarray -t monitors <<< "$monitors"
|
|
local displays=""
|
|
|
|
for ((i = 0; i < ${#monitors[@]}; i++)); do
|
|
((num = i + 1))
|
|
displays="${displays}\n${num}: ${monitors[$i]}"
|
|
done
|
|
|
|
displays=$(sed 's/\\n//' <<< $displays)
|
|
echo -e "$displays"
|
|
|
|
exit 0
|
|
}
|
|
|
|
print_not_int() {
|
|
regex=$(echo "$1" | sed -e "s/^-\{1,2\}//")
|
|
|
|
echo "gsu: value for '$regex' is not an integer"
|
|
exit 1
|
|
}
|
|
|
|
print_invalid_option() {
|
|
regex=$(echo "$1" | sed -e "s/^-\{1,2\}//")
|
|
|
|
echo "\
|
|
gsu: invalid option -- '$regex'
|
|
Try 'gsu --help' for more information."
|
|
|
|
exit 1
|
|
}
|
|
|
|
print_invalid_operand() {
|
|
echo "\
|
|
gsu: invalid operand -- '$1'
|
|
Valid characters: alphanumeric, dot, slash, underscore, dash."
|
|
|
|
exit 1
|
|
}
|
|
|
|
print_missing_deps() {
|
|
local args=""
|
|
|
|
for arg in "$@"; do
|
|
args="$args '$arg'"
|
|
done
|
|
|
|
args=$(sed 's/ //' <<< $args)
|
|
echo "gsu: missing required dependency(s) -- $args"
|
|
|
|
exit 1
|
|
}
|
|
|
|
print_manually_quit() {
|
|
echo "gsu: manually quit from user action"
|
|
exit 1
|
|
}
|
|
|
|
get_args() {
|
|
while [[ "$1" ]]; do
|
|
case "$1" in
|
|
"-h" | "--help") print_help ;;
|
|
"-v" | "--version") print_version ;;
|
|
"-u" | "--upload") upload="$1" ;;
|
|
"-l" | "--list-displays") print_displays ;;
|
|
"--notify") notify_arg="$1" ;;
|
|
|
|
"-d" | "--display")
|
|
if [[ -t 0 ]]; then
|
|
display="$2"
|
|
else
|
|
display=$(cat)
|
|
display="${display:0:1}"
|
|
|
|
if [[ -z "$display" ]]; then
|
|
notify "gsu: display is empty, assuming quit from dmenu or rofi"
|
|
exit 1
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
"-r" | "--dmenu")
|
|
if type -p dmenu >/dev/null; then
|
|
dmenu_type=(dmenu)
|
|
fi
|
|
|
|
if type -p rofi >/dev/null; then
|
|
dmenu_type=(rofi -dmenu -p "gsu > ")
|
|
fi
|
|
|
|
if [[ -z "$dmenu_type" ]]; then
|
|
print_missing_deps "dmenu or rofi"
|
|
else
|
|
case $(echo -e "Take a screenshot\nRecord a video\nRecord a video and covert it to the gif format" | "${dmenu_type[@]}") in
|
|
"Take a screenshot") set_type "screenshot" ;;
|
|
"Record a video") set_type "video" ;;
|
|
"Record a video and covert it to the gif format") set_type "gif" ;;
|
|
*) print_manually_quit ;;
|
|
esac
|
|
fi
|
|
;;
|
|
|
|
"-s" | "--screenshot") set_type "screenshot" ;;
|
|
"-m" | "--video") set_type "video" ;;
|
|
"-g" | "--gif") set_type "gif" ;;
|
|
"-n" | "--nocursor") nocursor="-u"; draw_mouse="-draw_mouse 0" ;;
|
|
|
|
"-c" | "--countdown")
|
|
if ! [[ $2 =~ ^[0-9]+$ ]]; then
|
|
print_not_int "$1"
|
|
fi
|
|
|
|
countdown_enable="$2"
|
|
;;
|
|
|
|
-*) print_invalid_option "$1" ;;
|
|
|
|
*)
|
|
if [[ "$1" =~ ^[a-zA-Z0-9_.\/-]+$ ]]; then
|
|
operand="$1"
|
|
else
|
|
print_invalid_operand "$1"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
shift
|
|
done
|
|
|
|
set_type "screenshot"
|
|
|
|
if [[ -z "$operand" ]]; then
|
|
local ext=""
|
|
|
|
case "$type" in
|
|
"screenshot") ext=".png" ;;
|
|
"video") ext=".mp4" ;;
|
|
"gif") ext=".gif" ;;
|
|
esac
|
|
|
|
mkdir -p "$config_path/imgs"
|
|
operand="$config_path/imgs/$(date +"%F.%H-%M-%S.%N")$ext"
|
|
fi
|
|
}
|
|
|
|
get_config() {
|
|
mkdir -p "$config_path"
|
|
|
|
if [[ ! -f "$config_path/config.conf" ]]; then
|
|
if [[ -f "SYSCONFDIR/config.conf" ]]; then
|
|
cp "SYSCONFDIR/config.conf" "$config_path"
|
|
elif [[ -f "config/config.conf" ]]; then
|
|
cp "config/config.conf" "$config_path"
|
|
else
|
|
echo "gsu: unable to find the config file"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
source "$config_path/config.conf"
|
|
}
|
|
|
|
get_command() {
|
|
local MONW="0"
|
|
local MONH="0"
|
|
local MONX="0"
|
|
local MONY="0"
|
|
|
|
if [[ ! -z "$display" ]]; then
|
|
if ! type -p xrandr >/dev/null; then
|
|
print_missing_deps "xrandr"
|
|
fi
|
|
|
|
if ! type -p xdotool >/dev/null; then
|
|
print_missing_deps "xdotool"
|
|
fi
|
|
|
|
local MONITORS=$(xrandr | grep -o "[0-9]*x[0-9]*[+-][0-9]*[+-][0-9]*")
|
|
local XMOUSE=$(xdotool getmouselocation | awk -F "[: ]" '{print $2}')
|
|
local YMOUSE=$(xdotool getmouselocation | awk -F "[: ]" '{print $4}')
|
|
|
|
readarray -t MONITORS <<< "$MONITORS"
|
|
|
|
for ((i = 0; i < ${#MONITORS[@]}; i++)); do
|
|
local mon="${MONITORS[$i]}"
|
|
|
|
local TEMPMONW=$(echo -e $mon | awk -F "[x+]" '{print $1}')
|
|
local TEMPMONH=$(echo -e $mon | awk -F "[x+]" '{print $2}')
|
|
local TEMPMONX=$(echo -e $mon | awk -F "[x+]" '{print $3}')
|
|
local TEMPMONY=$(echo -e $mon | awk -F "[x+]" '{print $4}')
|
|
|
|
case "$display" in
|
|
"active")
|
|
if (($XMOUSE >= $TEMPMONX)); then
|
|
if (($XMOUSE <= $TEMPMONX + $TEMPMONW)); then
|
|
if (($YMOUSE >= $TEMPMONY)); then
|
|
if (($YMOUSE <= $TEMPMONY + $TEMPMONH)); then
|
|
MONW=$TEMPMONW
|
|
MONH=$TEMPMONH
|
|
MONX=$TEMPMONX
|
|
MONY=$TEMPMONY
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
if [[ "$display" =~ ^[0-9]+$ ]]; then
|
|
if (($display == $i + 1)); then
|
|
MONW=$TEMPMONW
|
|
MONH=$TEMPMONH
|
|
MONX=$TEMPMONX
|
|
MONY=$TEMPMONY
|
|
fi
|
|
else
|
|
if (($TEMPMONW > $MONW)); then
|
|
((MONW=$MONW + $TEMPMONW))
|
|
fi
|
|
|
|
if (($TEMPMONH > $MONH)); then
|
|
((MONH=$MONH + $TEMPMONH))
|
|
fi
|
|
|
|
if (($TEMPMONX + $TEMPMONW >= $MONW)); then
|
|
((MONW=$TEMPMONW + $TEMPMONX))
|
|
fi
|
|
|
|
if (($TEMPMONY + $TEMPMONH >= $MONH)); then
|
|
((MONH=$TEMPMONH + $TEMPMONY))
|
|
fi
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ "$MONW" == "0" ]] && [[ "$MONH" == "0" ]] && [[ "$MONX" == "0" ]] && [[ "$MONY" == "0" ]]; then
|
|
notify "gsu: unable to find your display"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
case "$type" in
|
|
"screenshot")
|
|
if [[ -z "$display" ]]; then
|
|
cmd="$(maim $nocursor -s "$operand" 2>&1>/dev/null)"
|
|
else
|
|
cmd="$(maim $nocursor -g "${MONW}x${MONH}+${MONX}+${MONY}" "$operand" 2>&1>/dev/null)"
|
|
fi
|
|
;;
|
|
|
|
"video" | "gif")
|
|
if [[ -z "$display" ]]; then
|
|
read -r X Y W H G ID < <(slop -f "%x %y %w %h %g %i")
|
|
|
|
if [[ -z "$X" ]] || [[ -z "$Y" ]] || [[ -z "$W" ]] || [[ -z "$H" ]]; then
|
|
print_manually_quit
|
|
fi
|
|
fi
|
|
|
|
if [[ ! -z "$countdown_enable" ]]; then
|
|
countdown "$countdown_enable" "Handing control over to ffmpeg in COUNTNUM seconds."
|
|
fi
|
|
|
|
if [[ -z "$AUDIO" ]]; then
|
|
if ! type -p pulseaudio >/dev/null; then
|
|
notify "
|
|
gsu: no audio device set, default device 'pulse' cannot be found
|
|
Either install pulseaudio,
|
|
or add your audio device to `$config_path/config.conf`."
|
|
|
|
exit 1
|
|
fi
|
|
|
|
AUDIO="pulse"
|
|
fi
|
|
|
|
echo "Press [q] or [Ctrl+C] to stop recording."
|
|
|
|
if [[ -z "$display" ]]; then
|
|
cmd=$(ffmpeg -y -f alsa -i $AUDIO -r 30 -f x11grab -show_region 1 $draw_mouse -s "$W"x"$H" -i :0.0+$X,$Y -c:v libx264 -crf 18 -preset:v ultrafast -c:a aac -b:a 192k -loglevel error "$output" 2>&1>/dev/null)
|
|
else
|
|
cmd=$(ffmpeg -y -f alsa -i $AUDIO -r 30 -f x11grab -show_region 1 $draw_mouse -s "$MONW"x"$MONH" -i :0.0+$MONX,$MONY -c:v libx264 -crf 18 -preset:v ultrafast -c:a aac -b:a 192k -loglevel error "$output" 2>&1>/dev/null)
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
set_type() {
|
|
if [[ -z "$type" ]]; then
|
|
type="$1"
|
|
fi
|
|
}
|
|
|
|
countdown() {
|
|
for ((i = $1; i > 0; i--)); do
|
|
echo -ne "\r${2//COUNTNUM/$i}"
|
|
sleep 1
|
|
done
|
|
|
|
echo
|
|
}
|
|
|
|
log() {
|
|
if [[ ! -d "$config_path/logs" ]]; then
|
|
mkdir -p "$config_path/logs"
|
|
fi
|
|
|
|
echo "$1" >"$config_path/logs/$(date +'%Y.%m.%d-%H.%M.%S.%N').log"
|
|
}
|
|
|
|
check_valid_exts() {
|
|
local filename="${operand##*/}"
|
|
local extension="${filename##*.}"
|
|
|
|
IFS="," read -ra exts <<< "$1"
|
|
|
|
for ext in "${exts[@]}"; do
|
|
if [[ "$extension" == "$ext" ]]; then
|
|
return
|
|
fi
|
|
done
|
|
|
|
notify "gsu: invalid $type file extension -- '$extension'"
|
|
exit 1
|
|
}
|
|
|
|
take_screenshot() {
|
|
if ! type -p maim >/dev/null; then
|
|
print_missing_deps "maim"
|
|
fi
|
|
|
|
get_command
|
|
|
|
if [[ ! -z "$cmd" ]]; then
|
|
if [[ "$cmd" == *"keystroke or right-click"* ]]; then
|
|
print_manually_quit
|
|
fi
|
|
|
|
log "$cmd"
|
|
|
|
notify "
|
|
gsu: maim returned an error
|
|
Check the most recent log in '$config_path/logs'."
|
|
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
take_video() {
|
|
local output="$operand"
|
|
|
|
if ! type -p slop >/dev/null; then
|
|
print_missing_deps "slop"
|
|
fi
|
|
|
|
if ! type -p ffmpeg >/dev/null; then
|
|
print_missing_deps "ffmpeg"
|
|
fi
|
|
|
|
if [[ "$type" == "gif" ]]; then
|
|
output="$operand.mp4"
|
|
fi
|
|
|
|
get_command
|
|
|
|
if [[ -z "$cmd" ]]; then
|
|
notify "Stopped recording."
|
|
else
|
|
log "$cmd"
|
|
|
|
notify "
|
|
gsu: ffmpeg returned an error
|
|
Check the most recent log in '$config_path/logs'."
|
|
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
make_gif() {
|
|
if ! type -p ffmpeg >/dev/null; then
|
|
print_missing_deps "ffmpeg"
|
|
fi
|
|
|
|
notify "Generating your GIF. This could take awhile depending on how large the selection was."
|
|
local cmd=$(ffmpeg -y -i "$operand.mp4" -vf "fps=30,palettegen" -loglevel error "/tmp/palette.png" 2>&1>/dev/null)
|
|
|
|
if [[ -z "$cmd" ]]; then
|
|
cmd=$(ffmpeg -y -i "$operand.mp4" -i "/tmp/palette.png" -filter_complex "fps=30,scale=flags=lanczos[x];[x][1:v]paletteuse" -loglevel error "$operand")
|
|
|
|
if [[ -z "$cmd" ]]; then
|
|
return
|
|
fi
|
|
fi
|
|
|
|
log "$cmd"
|
|
|
|
notify "
|
|
gsu: ffmpeg returned an error
|
|
Check the most recent log in '$config_path/logs'."
|
|
|
|
exit 1
|
|
}
|
|
|
|
upload() {
|
|
pushd $(dirname $operand) &>/dev/null
|
|
local absolute="$PWD/$(basename $operand)"
|
|
popd &>/dev/null
|
|
|
|
if ! type -p jq >/dev/null; then
|
|
print_missing_deps "jq"
|
|
fi
|
|
|
|
local cmd
|
|
|
|
case "$type" in
|
|
"screenshot")
|
|
if [[ -z "$S_UPLOAD" ]]; then
|
|
cmd=$(curl --progress-bar -F "randomname=true" -F "file=@$absolute" "https://uguu.se/api.php?d=upload-tool")
|
|
else
|
|
cmd=$(eval "${S_UPLOAD//GSUFILEOUT/$absolute}")
|
|
fi
|
|
;;
|
|
|
|
"video")
|
|
if [[ -z "$V_UPLOAD" ]]; then
|
|
if [[ -z "$STREAMABLE_USER" ]] || [[ -z "$STREAMABLE_PASS" ]]; then
|
|
notify "
|
|
gsu: invalid streamable credentials
|
|
Fill in your credentials in '$config_path/config.conf' and try again."
|
|
|
|
exit 1
|
|
else
|
|
cmd=$(curl --progress-bar -u "$STREAMABLE_USER:$STREAMABLE_PASS" -F "file=@$absolute" "https://api.streamable.com/upload" | jq -r ".shortcode")
|
|
|
|
if [[ "${#cmd}" == 5 ]]; then
|
|
cmd="https://streamable.com/$cmd"
|
|
fi
|
|
fi
|
|
else
|
|
cmd=$(eval "${V_UPLOAD//GSUFILEOUT/$absolute}")
|
|
fi
|
|
;;
|
|
|
|
"gif")
|
|
if [[ -z "$G_UPLOAD" ]]; then
|
|
local cmd=$(curl -s -X POST -H "Content-Type: application/json" "https://api.gfycat.com/v1/gfycats")
|
|
|
|
if echo "$cmd" | jq -e ".gfyname" &>/dev/null; then
|
|
local output=$(echo "$cmd" | jq -r ".gfyname")
|
|
cp "$absolute" "$output"
|
|
|
|
cmd=$(curl --progress-bar -w "%{http_code}" --upload-file "$output" "https://filedrop.gfycat.com")
|
|
rm "$output"
|
|
|
|
if [[ "$cmd" == "200" ]]; then
|
|
notify "Gfycat is processing..."
|
|
local count=0
|
|
|
|
while [[ "$(curl -s https://api.gfycat.com/v1/gfycats/fetch/status/$output | jq -e -r '.task')" != "complete" ]]; do
|
|
((count++))
|
|
|
|
if [[ $count == 300 ]]; then
|
|
notify "gsu: gfycat timed out while processing"
|
|
exit 1
|
|
fi
|
|
|
|
sleep 1
|
|
done
|
|
|
|
cmd="https://gfycat.com/$output"
|
|
else
|
|
notify "gsu: gfycat returned an invalid http code -- '$cmd'"
|
|
exit 1
|
|
fi
|
|
fi
|
|
else
|
|
cmd=$(eval "${G_UPLOAD//GSUFILEOUT/$absolute}")
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
if type -p xclip >/dev/null && [[ "$cmd" == "http"* ]]; then
|
|
echo -n "$cmd" | xclip
|
|
echo -n "$cmd" | xclip -sel clip
|
|
fi
|
|
|
|
notify "$cmd"
|
|
}
|
|
|
|
main() {
|
|
get_config
|
|
get_args "$@"
|
|
|
|
case "$type" in
|
|
"screenshot")
|
|
if [[ -z "$S_UPLOAD" ]]; then
|
|
check_valid_exts "png,jpg"
|
|
fi
|
|
|
|
take_screenshot
|
|
;;
|
|
|
|
"video")
|
|
if [[ -z "$V_UPLOAD" ]]; then
|
|
check_valid_exts "mp4,mkv,mov"
|
|
fi
|
|
|
|
take_video
|
|
;;
|
|
|
|
"gif")
|
|
if [[ -z "$G_UPLOAD" ]]; then
|
|
check_valid_exts "gif"
|
|
fi
|
|
|
|
take_video
|
|
make_gif
|
|
;;
|
|
esac
|
|
|
|
if [[ ! -z "$upload" ]]; then
|
|
upload
|
|
fi
|
|
|
|
exit 0
|
|
}
|
|
|
|
main "$@"
|