123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- #!/bin/bash
- usage() {
- [[ "$@" != "" ]] && echo -e "\n$@\n\nTry -h for help.\n" || \
- cat <<EOF
- Simplistic Silent ScreenCast (GIF)
- Dependencies
- Either
- - maim and bc and imagemagick's convert for method 1
- or
- - ffmpeg for method 2
- Optional dependencies
- - slop for interactive region selection
- - gifsicle for optimisation (only method 2)
- Options
- -g str pass geometry string instead of interactively selecting it
- -w int wait int seconds before starting (default: $wait)
- -f int set frames per second (default: $fps)
- -d int set duration of recording in seconds instead of quitting interactively
- -o str define output file (default: $output)
- -m 1|2 define method to use:
- 1 take screenshots with maim and stitch them together to a looping
- GIF. Try to record the exact number of frames per second, but if
- the screenshoting takes too long, don't try to keep up.
- this method is more resource intensive but produces _much_ smaller
- files.
- 2 ffmpeg for both screencast and GIF conversion. If gifsicle is
- installed, optimise. Even optimised, filesize is 5x - 20x larger
- than with method 1.
- currently defaults to method $method.
- -v don't convert intermediate video to gif. only applies to method 2
- -h this text
- EOF
- exit 1
- }
- red() {
- # bright red text
- tput setaf 9
- echo "$*"
- tput sgr0
- }
- #~ method1() {
- #~ echo "Taking a series of screenshots with maim..."
- #~ index=0
- #~ while :; do
- #~ # how long to take a screenshot...
- #~ T="$({ time maim -m 1 -g $geom $tmpdir/$(printf "%09d" $index).png >/dev/null 2>&1; } 2>&1; )"
- #~ T="${T/.}" # now it's in milliseconds
- #~ T="${T##+(0)}" # strip leading zeros - see shopt -s extglob
- #~ # ...subtract this from the delay (=fps).
- #~ # to avoid using bc (i hope it's much faster that way, keeping the end result pure)
- #~ # we simply remove the dot '.', do our calculation, then put it back.
- #~ # this works because both time and bc have been told to use a precision of 3
- #~ # digits after the dot.
- #~ T="$(( delay - T ))"
- #~ [[ "${T:0:1}" == "-" ]] && continue # nothing to do if result is negative
- #~ ((index++))
- #~ (( duration == index / fps )) && break
- #~ sleep ".$(printf "%03d" $T)"
- #~ done & pid=$!
- #~ if ((duration == 0)); then
- #~ REPLY=''
- #~ while [[ "$REPLY" != "q" ]]; do read -n1; sleep 0.2; done
- #~ kill $pid >/dev/null 2>&1
- #~ echo "'q' pressed, finishing up..."
- #~ else
- #~ wait $pid
- #~ fi
- #~ echo "Converting screenshots to GIF with imagemagick's convert..."
- #~ convert -layers OptimizePlus -delay $(( delay / 10 )) $tmpdir/*.png -loop 0 "$output"
- #~ }
- method1() {
- echo "Taking a series of screenshots with maim..."
- index=0
- sleepdelay="$(bc<<<"scale=4;1/$fps")"
- while :; do
- coproc maim -m 1 -g $geom $tmpdir/$(printf "%09d" $index).png
- sleep $sleepdelay
- wait $COPROC_PID # see man bash - coprocesses, wait builtin
- # it should be done by now! if it really has to wait it means the system is congested and the fps needs to be lowered.
- #~ echo wait returned $!
- ((index++))
- (( duration == index / fps )) && break
- done >&2 & pid=$!
- if ((duration == -1)); then
- red "Press 'q' to finish "
- while [[ "$X" != "q" ]]; do read -sn1 X; sleep 0.2; done
- kill $pid >/dev/null 2>&1
- echo "'q' pressed, finishing up..."
- else
- wait $pid
- fi
- echo "Converting screenshots to GIF with imagemagick's convert..."
- convert -layers OptimizePlus -delay $(( delay / 10 )) $tmpdir/*.png -loop 0 "$output"
- }
- method2() {
- palette="$tmpdir/palette.png"
- [[ "$video" == 1 ]] && output="sssc$now.mkv" || output="$tmpdir/video.mkv"
- time=''
- ((duration > 0)) && time="-t $duration"
- echo "Recording .mkv video with ffmpeg..."
- red "Press 'q' to finish "
- wh="${geom%%+*}"
- xy="${geom#*+}"
- xy="${xy/+/,}"
- ffmpeg -hide_banner -f x11grab -framerate "$fps" -video_size ${wh} $time -i :0.0+${xy} -c:v libx264rgb -preset ultrafast -y "$output"
- if [[ "$video" == 0 ]]; then
- echo "Converting video to GIF with ffmpeg..."
- filters="fps=$fps"
- # build palette
- ffmpeg -hide_banner -v warning -i "$output" -framerate "$fps" -vf "$filters,palettegen" -y "$palette"
- # convert to gif
- ffmpeg -hide_banner -v warning -i "$output" -i "$palette" -framerate "$fps" -lavfi "$filters [x]; [x][1:v] paletteuse=dither=none" "sssc$now.gif"
- # optimize
- which gifsicle >/dev/null 2>&1 && gifsicle -V -O3 --delay $(( delay / 10 )) "sssc$now.gif" -o "sssc${now}_optimized.gif"
- fi
- }
- # sleep as a builtin
- for file in /usr/lib*/bash/sleep; do
- [ -r "$file" ] && enable -f "$file" sleep && break
- done
- # Portable enough?
- printf -v now "%(%Y%m%d%H%M%S)T"
- tmpdir="/tmp/sssc$now"
- mkdir -p "$tmpdir"
- trap "rm -r $tmpdir" EXIT
- output=sssc$now.gif
- geom=''
- fps='10'
- wait=2
- duration=-1
- TIMEFORMAT='%3R' # see man bash /TIMEFORMAT - needed for 'time'
- min=2
- max=50
- method=1
- video=0
- while getopts "g:w:f:d:o:m:vh" opt; do
- case $opt in
- g) geom="$OPTARG"
- [[ "$geom" != [0-9]*x[0-9]*\+[0-9]*\+[0-9]* ]] && usage "$geom is not a valid geometry string"
- ;;
- w) (( OPTARG > 0 )) || usage "wait is $OPTARG, but must be larger than zero"
- wait="$OPTARG"
- ;;
- f) (( OPTARG >= $min )) && (( OPTARG <= $max )) || usage "fps is $OPTARG, but must be between $min and $max"
- fps="$OPTARG"
- ;;
- d) (( OPTARG > 0 )) || usage "duration is $OPTARG, but must be larger than zero"
- duration="$OPTARG"
- ;;
- o) output="${OPTARG##*.}"
- output="${output,,}"
- [[ "$output" != "gif" ]] && usage "\"$OPTARG\" - invalid extension. Must be .gif or .GIF"
- output="$OPTARG"
- ;;
- m) case "$OPTARG" in
- '2') method=2
- ;;
- '1') which maim >/dev/null 2>&1 || usage "Can't find 'maim' in PATH. Maybe try '-m 2'?"
- ;;
- *) usage "method is $method, but must be either 1 or 2"
- ;;
- esac
- ;;
- v) video=1
- ;;
- h) usage
- ;;
- esac
- done
- [[ "$geom" == "" ]] && which slop >/dev/null 2>&1 || usage "No interactive selection without 'slop'"
- #~ shopt -s extglob # needed for Time calculations - all in an effort to avoid bc
- echo "Simplistic Silent Screencasting (GIF)
- ====================================="
- while [[ "$geom" == "" ]]; do
- printf "\nPlease select a region. "
- geom="$(slop 2>/dev/null)"
- done
- printf "\nRecording region $geom @ ${fps}frames/s\nstarting in $wait seconds. "
- sleep $wait
- if ((duration > 0)); then
- printf "\nRecording now for $duration seconds."
- else
- printf "\nPress 'q' to stop recording"
- fi
- printf "\n"
- delay="$(( 1000 / $fps ))" # now it's in milliseconds
- method$method
|