sssc 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #!/bin/bash
  2. usage() {
  3. [[ "$@" != "" ]] && echo -e "\n$@\n\nTry -h for help.\n" || \
  4. cat <<EOF
  5. Simplistic Silent ScreenCast (GIF)
  6. Dependencies
  7. Either
  8. - maim and bc and imagemagick's convert for method 1
  9. or
  10. - ffmpeg for method 2
  11. Optional dependencies
  12. - slop for interactive region selection
  13. - gifsicle for optimisation (only method 2)
  14. Options
  15. -g str pass geometry string instead of interactively selecting it
  16. -w int wait int seconds before starting (default: $wait)
  17. -f int set frames per second (default: $fps)
  18. -d int set duration of recording in seconds instead of quitting interactively
  19. -o str define output file (default: $output)
  20. -m 1|2 define method to use:
  21. 1 take screenshots with maim and stitch them together to a looping
  22. GIF. Try to record the exact number of frames per second, but if
  23. the screenshoting takes too long, don't try to keep up.
  24. this method is more resource intensive but produces _much_ smaller
  25. files.
  26. 2 ffmpeg for both screencast and GIF conversion. If gifsicle is
  27. installed, optimise. Even optimised, filesize is 5x - 20x larger
  28. than with method 1.
  29. currently defaults to method $method.
  30. -v don't convert intermediate video to gif. only applies to method 2
  31. -h this text
  32. EOF
  33. exit 1
  34. }
  35. red() {
  36. # bright red text
  37. tput setaf 9
  38. echo "$*"
  39. tput sgr0
  40. }
  41. #~ method1() {
  42. #~ echo "Taking a series of screenshots with maim..."
  43. #~ index=0
  44. #~ while :; do
  45. #~ # how long to take a screenshot...
  46. #~ T="$({ time maim -m 1 -g $geom $tmpdir/$(printf "%09d" $index).png >/dev/null 2>&1; } 2>&1; )"
  47. #~ T="${T/.}" # now it's in milliseconds
  48. #~ T="${T##+(0)}" # strip leading zeros - see shopt -s extglob
  49. #~ # ...subtract this from the delay (=fps).
  50. #~ # to avoid using bc (i hope it's much faster that way, keeping the end result pure)
  51. #~ # we simply remove the dot '.', do our calculation, then put it back.
  52. #~ # this works because both time and bc have been told to use a precision of 3
  53. #~ # digits after the dot.
  54. #~ T="$(( delay - T ))"
  55. #~ [[ "${T:0:1}" == "-" ]] && continue # nothing to do if result is negative
  56. #~ ((index++))
  57. #~ (( duration == index / fps )) && break
  58. #~ sleep ".$(printf "%03d" $T)"
  59. #~ done & pid=$!
  60. #~ if ((duration == 0)); then
  61. #~ REPLY=''
  62. #~ while [[ "$REPLY" != "q" ]]; do read -n1; sleep 0.2; done
  63. #~ kill $pid >/dev/null 2>&1
  64. #~ echo "'q' pressed, finishing up..."
  65. #~ else
  66. #~ wait $pid
  67. #~ fi
  68. #~ echo "Converting screenshots to GIF with imagemagick's convert..."
  69. #~ convert -layers OptimizePlus -delay $(( delay / 10 )) $tmpdir/*.png -loop 0 "$output"
  70. #~ }
  71. method1() {
  72. echo "Taking a series of screenshots with maim..."
  73. index=0
  74. sleepdelay="$(bc<<<"scale=4;1/$fps")"
  75. while :; do
  76. coproc maim -m 1 -g $geom $tmpdir/$(printf "%09d" $index).png
  77. sleep $sleepdelay
  78. wait $COPROC_PID # see man bash - coprocesses, wait builtin
  79. # 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.
  80. #~ echo wait returned $!
  81. ((index++))
  82. (( duration == index / fps )) && break
  83. done >&2 & pid=$!
  84. if ((duration == -1)); then
  85. red "Press 'q' to finish "
  86. while [[ "$X" != "q" ]]; do read -sn1 X; sleep 0.2; done
  87. kill $pid >/dev/null 2>&1
  88. echo "'q' pressed, finishing up..."
  89. else
  90. wait $pid
  91. fi
  92. echo "Converting screenshots to GIF with imagemagick's convert..."
  93. convert -layers OptimizePlus -delay $(( delay / 10 )) $tmpdir/*.png -loop 0 "$output"
  94. }
  95. method2() {
  96. palette="$tmpdir/palette.png"
  97. [[ "$video" == 1 ]] && output="sssc$now.mkv" || output="$tmpdir/video.mkv"
  98. time=''
  99. ((duration > 0)) && time="-t $duration"
  100. echo "Recording .mkv video with ffmpeg..."
  101. red "Press 'q' to finish "
  102. wh="${geom%%+*}"
  103. xy="${geom#*+}"
  104. xy="${xy/+/,}"
  105. ffmpeg -hide_banner -f x11grab -framerate "$fps" -video_size ${wh} $time -i :0.0+${xy} -c:v libx264rgb -preset ultrafast -y "$output"
  106. if [[ "$video" == 0 ]]; then
  107. echo "Converting video to GIF with ffmpeg..."
  108. filters="fps=$fps"
  109. # build palette
  110. ffmpeg -hide_banner -v warning -i "$output" -framerate "$fps" -vf "$filters,palettegen" -y "$palette"
  111. # convert to gif
  112. ffmpeg -hide_banner -v warning -i "$output" -i "$palette" -framerate "$fps" -lavfi "$filters [x]; [x][1:v] paletteuse=dither=none" "sssc$now.gif"
  113. # optimize
  114. which gifsicle >/dev/null 2>&1 && gifsicle -V -O3 --delay $(( delay / 10 )) "sssc$now.gif" -o "sssc${now}_optimized.gif"
  115. fi
  116. }
  117. # sleep as a builtin
  118. for file in /usr/lib*/bash/sleep; do
  119. [ -r "$file" ] && enable -f "$file" sleep && break
  120. done
  121. # Portable enough?
  122. printf -v now "%(%Y%m%d%H%M%S)T"
  123. tmpdir="/tmp/sssc$now"
  124. mkdir -p "$tmpdir"
  125. trap "rm -r $tmpdir" EXIT
  126. output=sssc$now.gif
  127. geom=''
  128. fps='10'
  129. wait=2
  130. duration=-1
  131. TIMEFORMAT='%3R' # see man bash /TIMEFORMAT - needed for 'time'
  132. min=2
  133. max=50
  134. method=1
  135. video=0
  136. while getopts "g:w:f:d:o:m:vh" opt; do
  137. case $opt in
  138. g) geom="$OPTARG"
  139. [[ "$geom" != [0-9]*x[0-9]*\+[0-9]*\+[0-9]* ]] && usage "$geom is not a valid geometry string"
  140. ;;
  141. w) (( OPTARG > 0 )) || usage "wait is $OPTARG, but must be larger than zero"
  142. wait="$OPTARG"
  143. ;;
  144. f) (( OPTARG >= $min )) && (( OPTARG <= $max )) || usage "fps is $OPTARG, but must be between $min and $max"
  145. fps="$OPTARG"
  146. ;;
  147. d) (( OPTARG > 0 )) || usage "duration is $OPTARG, but must be larger than zero"
  148. duration="$OPTARG"
  149. ;;
  150. o) output="${OPTARG##*.}"
  151. output="${output,,}"
  152. [[ "$output" != "gif" ]] && usage "\"$OPTARG\" - invalid extension. Must be .gif or .GIF"
  153. output="$OPTARG"
  154. ;;
  155. m) case "$OPTARG" in
  156. '2') method=2
  157. ;;
  158. '1') which maim >/dev/null 2>&1 || usage "Can't find 'maim' in PATH. Maybe try '-m 2'?"
  159. ;;
  160. *) usage "method is $method, but must be either 1 or 2"
  161. ;;
  162. esac
  163. ;;
  164. v) video=1
  165. ;;
  166. h) usage
  167. ;;
  168. esac
  169. done
  170. [[ "$geom" == "" ]] && which slop >/dev/null 2>&1 || usage "No interactive selection without 'slop'"
  171. #~ shopt -s extglob # needed for Time calculations - all in an effort to avoid bc
  172. echo "Simplistic Silent Screencasting (GIF)
  173. ====================================="
  174. while [[ "$geom" == "" ]]; do
  175. printf "\nPlease select a region. "
  176. geom="$(slop 2>/dev/null)"
  177. done
  178. printf "\nRecording region $geom @ ${fps}frames/s\nstarting in $wait seconds. "
  179. sleep $wait
  180. if ((duration > 0)); then
  181. printf "\nRecording now for $duration seconds."
  182. else
  183. printf "\nPress 'q' to stop recording"
  184. fi
  185. printf "\n"
  186. delay="$(( 1000 / $fps ))" # now it's in milliseconds
  187. method$method