#! /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 (" ") 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