home/bin/slip
2021-01-24 15:16:13 +01:00

355 lines
11 KiB
Bash
Executable File

#! /bin/bash
set -eo pipefail
VERSION="2.0.4"
# Sane defaults in case of not being set in the config / config not existing.
CREDENTIALS_FILE="$HOME/.config/slip/credentials"
RECORD_PIDFILE="/tmp/slip_record.pid"
IMAGES="$HOME/Pictures"
VIDEOS="$HOME/Videos"
GIF_SCALE="640"
GIF_FPS=15
SOUND=0
NOUPLOAD=0
DMENU_CMD="rofi -dmenu -p slip"
# Load config.
CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/slip/config"
if [ -f $CONFIG ]; then
source $CONFIG
else
echo "No config was found - using default options. Please copy the example configuration to ~/.config/slip/config from https://github.com/Toqozz/slip"
fi
CLIENT_ID="abd3a90bbfb65e9"
# Dmenu prompts.
DMENU_OPTS="screenshot
video
gif
exit"
DMENU_RECORD_OPTS="stop
cancel"
DMENU_UPLOAD_OPTS_BIG="upload (gfycat)
delete
exit"
DMENU_UPLOAD_OPTS="upload (imgur)
upload (gfycat)
delete
exit"
# Maximum size for upload (kb).
# Gfycat does not have a file limit.
MAX_GIF_SIZE_IMGUR=10000
function usage() {
echo ""
echo " slip [ -h | -v | -s | -r | -a | -q ] -nu"
echo " Uploads images taken with ffmpeg via slop to imgur. Quick video recording."
echo " That's all."
echo ""
echo " -h | --help show this help"
echo " -v | --version show version"
echo " -s | --screenshot take a screenshot (skip dmenu)"
echo " -g | --gif take a gif (skip dmenu)"
echo " -r | --record take a video (skip dmenu)"
echo " -nu | --no-upload don't upload the result"
echo " -q | --stop stop recording a video / gif (skip dmenu) (only when recording)"
echo ""
}
function upload_imgur() {
file="$1"
if [ -f "$file" ]; then
if [ -f "$CREDENTIALS_FILE" ]; then
# We have an auth token to use.
check_auth_credentials
if [ $? -ne 1 ]; then
# Token valid.
curl -sH "Authorization: Bearer ${access_token}" -F "image=@$file" "https://api.imgur.com/3/upload"
fi
else
# Imgur needs to know what program is using its services.
curl -sH "Authorization: Client-ID ${CLIENT_ID}" -F "image=@$file" "https://api.imgur.com/3/upload"
fi
else
echo "File does not exist, what happened?"
fi
}
function upload_gfycat() {
local file="$1"
local result=$(curl -s -XPOST https://api.gfycat.com/v1/gfycats -H "Content-Type: application/json")
local gfyname=$(parse "gfycat" $result)
curl -s "https://filedrop.gfycat.com/$gfyname" --upload-file "$file"
# No new line so we can append it easily.
echo -n "$gfyname"
}
function check_auth_credentials() {
token_expire_time=0
if [ -f "${CREDENTIALS_FILE}" ]; then
source "${CREDENTIALS_FILE}"
fi
if [ ! -z ${access_token} ]; then
current_time="$(date +%s)"
preemptive_refresh_time="$((10*60))"
expired="$((current_time > (token_expire_time - preemptive_refresh_time)))"
if [ "${expired}" -eq "0" ]; then
#token has expired, get a new one.
echo 'The authorization token has expired, please get a new one.'
return 1;
fi
else
return 1;
fi
}
function clip_clear() {
# Clear x cliboard selection.
xsel -bc # Ctrl-v / Shift-Insert.
# xsel -pc # Middle click.
}
# Run slop and get the geometry line.
function slop_geom() {
if [ "$1" = "image" ]; then
slop -f '%wx%h+%x+%y'
else
slop -f '%wx%h|%x,%y' # | sed -e s/+/\|/ -e s/+/,/
fi
#elif [ "$1" = "video" ]; then
#slop -f '%wx%h+%x+%y' | sed -e s/+/\|/ -e s/+/,/
#slop | sed -n 5p | sed -e s/G=// -e s/+/\|/ -e s/+/,/
#elif [ "$1" = "gif" ]; then
#slop -f '%wx%h+%x+%y' | sed -e s/+/\|/ -e s/+/,/
}
# Take the shot (or start the video.)
function shot() {
if [ "$1" = "image" ]; then
extension=".png" # img-2016-04-16-153906.png
filename="$IMAGES/img-`date +%Y-%m-%d-%H%M%S`$extension" # .png, .jpg
maim -g "$2" $filename
# TODO, do we want more dependencies? (optional dependencies?)
#ffmpeg -f x11grab -video_size "$2" -i "$DISPLAY+$3" -vframes 1 $filename &> /dev/null
elif [ "$1" = "video" ]; then
extension=".mkv" # vid-2016-04-16-153906.mkv
filename="$VIDEOS/vid-`date +%Y-%m-%d-%H%M%S`$extension" # .mkv, .mp4
if [ $SOUND == 0 ]; then
ffmpeg -f x11grab -video_size "$2" -framerate 60 -i "$DISPLAY+$3" -loglevel quiet -c:v libx264 -preset ultrafast $filename &
else
ffmpeg -f pulse -i 1 -f x11grab -video_size "$2" -framerate 60 -i "$DISPLAY+$3" -loglevel quiet -c:v libx264 -preset ultrafast $filename &
fi
echo "$! vid $filename" > "$RECORD_PIDFILE"
elif [ "$1" = "gif" ]; then
extension=".gif" # gif-2016-04-16-153906.gif
tmpfilename="/tmp/gif-`date +%Y-%m-%d-%H%M%S-tmp`.mkv"
filename="$VIDEOS/gif-`date +%Y-%m-%d-%H%M%S`$extension" # .gif
# Record a video to be converted to gif.
ffmpeg -f x11grab -video_size "$2" -framerate $GIF_FPS -i "$DISPLAY+$3" -loglevel quiet -c:v libx264 -preset ultrafast $tmpfilename &
#ffmpeg -f x11grab -video_size "$2" -framerate 15 -i "$DISPLAY+$3" -vf scale="$GIF_SCALE:-1" $filename &> /dev/null &
# We need to access this information later.
ratio=$(awk -F"x" '{ print int($1/$2) }' <<< "$2") # We only really need a binary answer.
if [ "$ratio" -ge 1 ]; then echo "$! gif $tmpfilename $filename 0" > "$RECORD_PIDFILE"
else echo "$! gif $tmpfilename $filename 1" > "$RECORD_PIDFILE"
fi
fi
}
function kill_ffmpeg() {
# Kill ffmpeg (stopping the recording.)
kill $1 || echo "Failed to kill ffmpeg, did it crash? Removing $RECORD_PIDFILE anyway."
# Remove the pid file so that slip can be used as normal again.
rm "$RECORD_PIDFILE"
}
function convert_to_gif() {
local tfn=$1
local pfn=$2
local fn=$3
local wh=$4 # 0 = width larger than height, 1 = height larger than width
local ratio
# If the width is larger than the height, we want to scale with the width, otherwise scale with the height.
if [ $wh -eq 0 ]; then
ratio="$GIF_SCALE:-1"
else
ratio="-1:$GIF_SCALE"
fi
notify "Converting to gif…"
# Give enough time to save the file.
sleep 1 && ffmpeg -i "$tfn" -loglevel quiet -vf fps=$GIF_FPS,scale="$ratio":flags=lanczos,palettegen "$pfn" &&
ffmpeg -i "$tfn" -i "$pfn" -loglevel quiet -filter_complex "fps=$GIF_FPS,scale=$ratio:flags=lanczos[x];[x][1:v]paletteuse" "$fn" &&
echo "$fn"
}
function stop_rec() {
local choice
local pid=$1 # Process id (for killing ffmpeg).
local tfn=$3 # Temp file name (for gifs).
local fn=$4 # File name for the gif/vid we're saving.
local pfn="${tfn}-palette.png" # Palette file name (for gif rendering).
# Stop recording.
kill_ffmpeg "$pid"
# When processing a gif, we canvert it straight away and then upload.
# NOTE: gfycat does not require the video to be converted, but we convert it anyway to store
# the .gif file.
# Is this actually a good idea? We could just store the .mkv instead, but then we lose the ability to store .gif.
if [ "$2" = "gif" ]; then
fn=$(convert_to_gif "$tfn" "$pfn" "$fn" "$5")
size=$(du -k $fn | cut -f1)
if [ $NOUPLOAD == 0 ]; then
if [ "$size" -lt "${MAX_GIF_SIZE_IMGUR}" ]; then
choice=$($DMENU_CMD <<< $DMENU_UPLOAD_OPTS)
else
choice=$($DMENU_CMD <<< $DMENU_UPLOAD_OPTS_BIG)
fi
if [ "$choice" = "upload (gfycat)" ]; then
clip_clear
name=$(upload_gfycat "$tfn")
url="https://gfycat.com/$name"
notify "Uploading to $url ..."
echo "$url"
echo -n "$url" | xsel -bi # Read to clipboard.
elif [ "$choice" = "upload (imgur)" ]; then
clip_clear
output=$(upload_imgur "$fn")
url=$(parse "imgur" "$output")
notify "$url"
echo "$url"
echo -n "$url" | xsel -bi # Read to clipboard.
elif [ "$choice" = "delete" ]; then
rm $fn
fi
fi
else
notify "$fn"
fi
}
# Parse x,y -- but also imgur.
function parse() {
if [ "$1" = "geometryx" ]; then
awk -F"|" '{ print $1 }' <<< "$2"
elif [ "$1" = "geometry+" ]; then
awk -F"|" '{ print $2 }' <<< "$2"
elif [ "$1" = "imgur" ]; then
jq -r ".data.link" <<< "$2"
#sed -e 's/.*\"link\":"\([^"]*\).*/\1/' -e 's/\\//g' <<< "$2"
#sed -e 's/\"link\":"\([^"]*\)/\1/' -e 's/\\//g' <<< "$2"
elif [ "$1" = "gfycat" ]; then
jq -r ".gfyname" <<< "$2"
fi
}
function notify() {
notify-send -t 7000 "slip" "$1"
}
function main() {
if [ "$1" = "screenshot" ]; then
# Run slop and get geometry from it.
# maim naturally supports slop's output coordinates.
geometry=$(slop_geom "image")
# Take the shot.
shot "image" "$geometry"
# Parse imgur json into link.
# If noupload is not set.
if [ $NOUPLOAD == 0 ]; then
#echo "uploading..."
# Clear cliboard before doing anything, so we can copy link to it later.
clip_clear
output=$(upload_imgur "$filename")
url=$(parse "imgur" "$output")
# Notify user that upload has finished.
notify "$url"
echo "$url"
# Read to clipboard, removing trailing newline.
echo -n "$url" | xsel -bi
else
notify "$filename"
fi
# echo "$url" | xsel -pi # Read to primary.
elif [ "$1" = "video" ]; then
geometry=$(slop_geom "video")
wxh=$(parse "geometryx" $geometry)
off=$(parse "geometry+" $geometry)
shot "video" "$wxh" "$off"
elif [ "$1" = "gif" ]; then
geometry=$(slop_geom "gif")
wxh=$(parse "geometryx" $geometry)
off=$(parse "geometry+" $geometry)
shot "gif" "$wxh" "$off"
elif [ "$1" = "stop" ]; then
# Get info of recording process.
local info
# Get infor from the pidfile.
info=$(cat "$RECORD_PIDFILE")
# Stop with parameters ("<pid> <type> <filename> <ratio>")
stop_rec $info
exit 0
else
exit 0
fi
}
# Dependencies.
depends="curl
jq
maim
slop
ffmpeg"
while read line
do
if ! type $line &> /dev/null ; then
echo "$line not found, expect unexpected or breakage."
fi
done <<< "$depends"
# Main.
# Yes, we could call this stuff with integers, but this is much clearer.
if [ "$1" = "-nu" -o "$1" = "--no-upload" -o "$2" = "-nu" -o "$2" = "--no-upload" ]; then
NOUPLOAD=1
fi
if [ "$1" = "-h" -o "$1" = "--help" ]; then
usage
exit 0
elif [ "$1" = "-v" -o "$1" = "--version" ]; then
echo "Version: $VERSION"
exit 0
elif [ "$1" = "-s" -o "$1" = "--screenshot" ]; then
main "screenshot"
elif [ "$1" = "-g" -o "$1" = "--gif" ]; then
main "gif"
elif [ "$1" = "-r" -o "$1" = "--record" ]; then
main "video"
elif [ "$1" = "-q" -o "$1" = "--stop" ]; then
main "stop"
elif [ $# == 0 ]; then
if [ -a "$RECORD_PIDFILE" ]; then
main $($DMENU_CMD <<< "$DMENU_RECORD_OPTS")
else
main $($DMENU_CMD <<< "$DMENU_OPTS")
fi
fi