winneon.gsu/gsu

685 lines
15 KiB
Bash
Executable file

#!/usr/bin/env bash
VERSION="1.3.2"
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.
-u, --upload Upload after running the utility.
--terminal Open interactive commands in a new terminal..
SET THIS IF RUNNING FROM ANYTHING OTHER THAN A TERM,
i.e. xbindkeys
--hide Minimize the current window while capturing.
This is useful when running with --terminal to hide
the newly opened term.
--notify Send a libnotify notification of the output.
UTILITY OPTIONS
-r, --dmenu Select the one of the below options from dmenu or rofi.
-s, --screenshot Take a screenshot. (default)
-m, --video Record a video of the screen.
-g, --gif Record a video and convert it to the gif format.
CAPTURE OPTIONS
-n, --no-cursor 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)
SCREENSHOT OPTIONS
-o, --no-opengl Disable OpenGL hardware-accelerated capture.
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() {
local prev_arg=""
while [[ "$1" ]]; do
case "$1" in
"-h" | "--help") print_help ;;
"-v" | "--version") print_version ;;
"-l" | "--list-displays") print_displays ;;
"-u" | "--upload") upload="$1" ;;
"--hide")
if ! type -p xdotool >/dev/null; then
print_missing_deps "xdotool"
fi
hide="$1"
;;
"--terminal")
if [[ -z "$GSU_TERM" ]]; then
notify "\
gsu: \$GSU_TERM is not set
Set this variable in '$config_path/config.conf'."
exit 1
fi
terminal_arg="$1"
;;
"--notify") notify_arg="$1" ;;
"-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
rofi_arg="$1"
fi
;;
"-s" | "--screenshot") set_type "screenshot" ;;
"-m" | "--video") set_type "video" ;;
"-g" | "--gif") set_type "gif" ;;
"-n" | "--no-cursor") nocursor="-u"; draw_mouse="-draw_mouse 0" ;;
"-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
;;
"-o" | "--no-opengl") noopengl="--noopengl" ;;
"-c" | "--countdown")
if ! [[ $2 =~ ^[0-9]+$ ]]; then
print_not_int "$1"
fi
countdown_enable="$2"
;;
-*) print_invalid_option "$1" ;;
*)
case "$prev_arg" in
"-d" | "--display" | "-c" | "--countdown") ;;
*)
if [[ "$1" =~ ^[a-zA-Z0-9_.\/-]+$ ]]; then
operand="$1"
else
print_invalid_operand "$1"
fi
;;
esac
;;
esac
prev_arg="$1"
shift
done
}
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
check_minimize
case "$type" in
"screenshot")
if [[ -z "$display" ]]; then
cmd="$(maim $noopengl $nocursor -s "$operand" 2>&1>/dev/null)"
else
cmd="$(maim $noopengl $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
check_maximize
print_manually_quit
fi
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`."
check_maximize
exit 1
fi
AUDIO="pulse"
fi
if [[ ! -z "$countdown_enable" ]]; then
check_maximize
countdown "$countdown_enable" "Handing control over to ffmpeg in COUNTNUM seconds."
check_minimize
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
check_maximize
}
set_type() {
if [[ -z "$type" ]]; then
type="$1"
fi
}
check_minimize() {
if [[ ! -z "$hide" ]]; then
xdotool_id=$(xdotool getactivewindow)
xdotool windowminimize $xdotool_id
sleep 0.5
fi
}
check_maximize() {
if [[ ! -z "$hide" ]] && [[ ! -z "$xdotool_id" ]]; then
xdotool windowactivate $xdotool_id
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
echo "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
echo "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
echo "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 xsel >/dev/null && [[ "$cmd" == "http"* ]]; then
echo -n "$cmd" | xsel -ib
fi
notify "$cmd"
}
remove_args() {
local args=()
while [[ "$1" ]]; do
if [[ "$1" == "--terminal" ]]; then
shift
else
args+=("$1")
shift
fi
done
echo "${args[@]}"
}
main() {
get_config
get_args "$@"
if [[ -z "$terminal_arg" ]]; then
if [[ ! -z "$rofi_arg" ]]; then
case $(echo -e "Take a screenshot\nRecord a video\nRecord a video and convert it to the gif format" | "${dmenu_type[@]}") in
"Take a screenshot") set_type "screenshot" ;;
"Record a video") set_type "video" ;;
"Record a video and convert it to the gif format") set_type "gif" ;;
*) print_manually_quit ;;
esac
fi
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
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
else
notify "gsu: saved to '$operand'"
fi
else
pushd $(dirname ${BASH_SOURCE[0]}) &>/dev/null
local absolute="$PWD/$(basename ${BASH_SOURCE[0]})"
popd &>/dev/null
local args=$(remove_args "$@")
for ((i = 0; i < ${#GSU_TERM[@]}; i++)); do
if [[ "${GSU_TERM[$i]}" == *"CMD"* ]]; then
GSU_TERM[$i]="${GSU_TERM[$i]//CMD/$absolute $args}"
fi
done
eval "${GSU_TERM[@]}"
fi
exit 0
}
main "$@"