├── LICENSE ├── Makefile ├── README.md ├── man └── giph.1 └── src └── giph /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Philipp Schaffrath 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr/local 2 | DESTDIR ?= 3 | BINDIR ?= $(PREFIX)/bin 4 | MANDIR ?= $(PREFIX)/share/man 5 | 6 | all: 7 | @echo "Nothing to do, try \"make install\" instead." 8 | 9 | install-common: 10 | @install -v -d "$(DESTDIR)$(MANDIR)/man1" && install -m 0644 -v man/giph.1 "$(DESTDIR)$(MANDIR)/man1/giph.1" 11 | 12 | install: install-common 13 | @install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v src/giph "$(DESTDIR)$(BINDIR)/giph" 14 | 15 | uninstall: 16 | @rm -vrf \ 17 | "$(DESTDIR)$(BINDIR)/giph" \ 18 | "$(DESTDIR)$(MANDIR)/man1/giph.1" 19 | 20 | .PHONY: install uninstall install-common 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # giph 2 | giph is a screen recorder that records the desktop, a window or selection and encodes it into a gif file. It prints the encoded gif directly to standard output when omitting the output filename. 3 | 4 | ![demo](https://i.imgur.com/Hoi0fF7.gif) 5 | 6 | *I used **giph** to record a gif of **giph** recording a gif*. 7 | 8 | ## Examples 9 | 10 | ```bash 11 | $ giph -s -l -c 1,1,1,0.3 -b 5 -p -5 out.gif 12 | ``` 13 | Select a window or area with slop. The selection rectangle is highlighted in a transparent blue color abd has a 5px border on the inside. 14 | After stopping the recording with either `ctrl+c`, by running `giph --stop` or by sending a `SIGINT` to the processgroup, the resulting gif is written to `out.gif`. 15 | 16 | 17 | ```bash 18 | $ giph -g 100x200+0+0 -d 5 -t 10 19 | ``` 20 | Records a 100x200 pixel rectangle in the top left corner of the screen. The recording starts after a 5 seconds countdown and will record for exactly 10 seconds. The resulting gif will be printed to standard output, which makes this able to be piped into other scripts like a file-upload to an image hosting service. 21 | 22 | 23 | ```bash 24 | $ giph -f 30 -t 5 -s -a -m out.webm 25 | ``` 26 | Records a 5 second video of the users selection at 30 fps. The recording also contains the users desktop audio and microphone. If the recording fails because the default audio source `0` is not the correct one, run `pacmd list-sources` to get the correct source `index` or `name` and pass it to the `-as` parameter instead of using `-a`. Example: `giph -f 30 -t 5 -s -as 1 -m out.webm` (using id) or `giph -f 30 -t 5 -s -as alsa_output.pci-0000_04_00.1.hdmi-stereo.monitor -m out.webm` (using name) 27 | 28 | ```bash 29 | $ giph -s -t 10 --format webm | curl -F "file=@-" 0x0.st | xclip -selection clipboard 30 | ``` 31 | 32 | Records a 10 second webm of the users selection, uploads the video to 0x0.st using curl and copies the returned url to the clipboard. 33 | 34 | ## Installation 35 | 36 | ### Arch 37 | 38 | ```bash 39 | $ yay -S giph 40 | ``` 41 | Or install [giph-git](https://aur.archlinux.org/packages/giph-git/) to get the latest development version. 42 | 43 | ### From source 44 | 45 | Make sure to install the following dependencies: 46 | 47 | - ffmpeg 48 | - xdotool 49 | 50 | Optionally, install the following dependencies: 51 | 52 | - slop (`--select`) 53 | - libnotify (`--notify`) 54 | - pgrep (`--stop`) 55 | 56 | Clone the giph repository: 57 | 58 | ```bash 59 | $ git clone https://github.com/phisch/giph.git 60 | ``` 61 | 62 | And finally install giph: 63 | 64 | ```bash 65 | $ cd giph 66 | $ sudo make install 67 | ``` 68 | 69 | ## Desktop Makers 70 | 71 | type=discord 72 | 73 | I am actively working on giph and other cool projects on the [Desktop Makers Discord](https://discord.gg/RqKTeA4uxW). It aims to be a community for communities of Linux desktop related projects. If you are looking to collaborate with or want to contribute to great projects, this might be the right place for you. 74 | -------------------------------------------------------------------------------- /man/giph.1: -------------------------------------------------------------------------------- 1 | .TH GIPH 1 "April 2019" "MIT License" "User Commands" 2 | .SH NAME 3 | giph \- record gif from desktop, window or selection 4 | .SH SYNOPSIS 5 | .B giph 6 | .RI [ OPTIONS "] [" FILENAME ] 7 | .br 8 | .B giph 9 | [\fB-g\fR \fIGEOMETRY\fR | 10 | \fB-w\fR \fIINT\fR | 11 | \fB-s\fR [\fB-l\fR] [\fB-c\fR \fIFLOAT,FLOAT,FLOAT,FLOAT\fR] [\fB-p\fR \fIFLOAT\fR] [\fB-b\fR \fIFLOAT\fR]] 12 | [\fB-d\fR \fIINT\fR] 13 | [\fB-t\fR \fIINT\fR] 14 | [\fB-f\fR \fIINT\fR] 15 | [\fB--format\fR \fISTRING\fR] 16 | [\fB-a\fR | \fB -as \fISTRING\fR] 17 | [\fB-m\fR | \fB -ms \fISTRING\fR] 18 | [\fIFILENAME\fR] 19 | .SH DESCRIPTION 20 | .B giph 21 | is a screen recorder that records the desktop, a window or selection and encodes it into a video file. 22 | .br 23 | The recorded video file is encoded in one of the supported formats, gif, webm, mp4 or mkv based on the file extension given in [\fIFILENAME\fR]. 24 | .br 25 | When omitting [\fIFILENAME\fR], the default format "gif" is used (use --format to overwrite), and the resulting video directly printed to standard output. 26 | .SH EXAMPLES 27 | .TP 28 | .BI "giph -g " "300x200+600+200 ~/Videos/$(date +%s).png" 29 | Records a 300x200 pixel (width x height) rectangle, that is shifted 600 pixel to the right and 200 pixel down from the top-left corner. The recording stops when either 30 | .B ctrl+c 31 | is pressed, or after calling 32 | .B giph --stop. 33 | .br 34 | The encoded gif will be saved in your users videos directory and has the current timestamp as a name. 35 | .SH OPTIONS 36 | .TP 37 | .BR \-h ", " \-\-help 38 | Print help and exit. 39 | .TP 40 | .BR \-\-stop 41 | Finish any running giph recordings 42 | .TP 43 | .BR \-\-version 44 | Print version and exit. 45 | .TP 46 | .BR \-v* ", " \-\-verbose ", " \-\-quiet 47 | Defines how verbose giph will be. Omitting [\fIFILENAME\fR] will overwrite the verbosity to \fI-1\fR (quiet). Use the following list to determine which option to use for each verbosity level: 48 | .in +2 49 | \(bu 50 | .IB "-1 \fR(quiet)" " --quiet \fR - logs errors" 51 | .br 52 | \(bu 53 | .IB " 0 \fR(normal) - like quiet, also displays interactive components" 54 | .br 55 | \(bu 56 | .IB " 1 \fR(verbose)" " -v\fR, \fB--verbose" "\fR - like normal, also logs basic information about steps" 57 | .br 58 | \(bu 59 | .IB " 2 \fR(very_verbose) " -vv "\fR - like verbose, also logs detailed information about steps" 60 | .br 61 | \(bu 62 | .IB " 3 \fR(debug) " -vvv "\fR - like very_verbose, also shows background process output" 63 | .TP 64 | .BR \-s ", " \-\-select 65 | Uses slop to interactively select the desired region or window to record. This uses the 66 | .B SLOP OPTIONS 67 | described below. 68 | .TP 69 | .BR \-g ", " \-\-geometry " " \fIGEOMETRY 70 | Sets the region to record. 71 | .TP 72 | .BR \-w ", " \-\-window " " \fIINT 73 | Sets the window id of the desired window to record. 74 | .TP 75 | .BR \-d ", " \-\-delay " " \fIINT 76 | Sets the time in seconds to wait before the recording starts. 77 | .TP 78 | .BR \-t ", " \-\-timer " " \fITIMEDURATION 79 | Sets a fixed time to record. The format is a timeduration as described in the ffmpeg documentation (https://ffmpeg.org/ffmpeg-utils.html#Time-duration). As an example, '10' would mean 10 seconds, '3:30' means 3 minutes and 30 seconds, '1:02:03' means 1 hour, 2 minutes and 3 seconds, and '5.5' means 5.5 seconds. 80 | .TP 81 | .BR \-f ", " \-\-framerate " \fIINT\fR (default: \fI20\fR)" 82 | Sets the desired framerate of the recorded gif. A higher framerate will result in a larger filesize. 83 | .TP 84 | .BR \-\-format 85 | Overwrites the format that should be used for the recording. The available formats are 86 | .IR gif ", " webm ", " mp4 " and " mkv "." 87 | While 88 | .IR gif 89 | is the only format that does not support audio recording. 90 | .TP 91 | .BR \-a ", " \-\-audio 92 | Enables audio recording for formats that support audio. 93 | .TP 94 | .BR \-as ", " \-\-audio-source " \fISTRING\fR (default: '\fI0\fR')" 95 | Define which pulseaudio source should be used for the recording. Run `pacmd list-sources` to get a list of all available sources. You can use either the index or the name of the source for this parameter. When setting this parameter, setting 96 | .BR \-a " or " \-\-audio 97 | can be omitted. 98 | .TP 99 | .BR \-m ", " \-\-microphone 100 | Enables microphone recording for formats that support audio. 101 | .TP 102 | .BR \-ms ", " \-\-microphone-source " \fISTRING\fR (default: '\fIdefault\fR')" 103 | Define which pulseaudio source should be used for the recording. Run `pacmd list-sources` to get a list of all available sources. You can use either the index or the name of the source for this parameter. When setting this parameter, setting 104 | .BR \-m " or " \-\-microphone 105 | can be omitted. 106 | .TP 107 | .BR \-y ", " \-\-notify 108 | Uses notify-send to send an urgent notification if an error happens, or a normal notification when the final gif was saved successfully. 109 | .SH SLOP OPTIONS 110 | When 111 | .BR -s " or " --select 112 | is used, slop will be used to get geometry information. Read 113 | .B slop(1) 114 | for more detailed information about slop options. The following options will be passed to slop if they are set: 115 | .TP 116 | .BR \-b ", " \-\-bordersize " \fIFLOAT\fR (default: \fI1\fR)" 117 | Set the selection border thickness. 118 | .TP 119 | .BR \-p ", " \-\-padding " \fIFLOAT\fR (default: \fI0\fR)" 120 | Set the selection padding. This can be negative. 121 | .TP 122 | .BR \-to ", " \-\-tolerance " \fIFLOAT\fR (default: \fI2\fR)" 123 | Defines how far the mouse can move after clicking while still being considered a click. 124 | .TP 125 | .BR \-c ", " \-\-color " \fIFLOAT,FLOAT,FLOAT,FLOAT\fR (default: \fI0.5,0.5,0.5,1\fR)" 126 | Set the selection color as RGB or ARGB. 127 | .TP 128 | .BR \-r ", " \-\-shader " \fISTRING" 129 | Set the shader to be used. 130 | .TP 131 | .BR \-n ", " \-\-nodecorations " \fIINT\fR (default: \fI0\fR)" 132 | .RI "Tries to select child windows. " 0 " is off, " 1 " tries to remove decorations, " 2 " aggressively tries to remove decorations." 133 | .TP 134 | .BR \-l ", " \-\-highlight 135 | .RB "Highlight the selection rectangle. This uses the color provided in " -c " or " --color "." 136 | .TP 137 | .BR \-k ", " \-\-nokeyboard 138 | Disable being able to cancel the selection using the keyboard. 139 | .TP 140 | .BR \-o ", " \-\-noopengl 141 | Disable graphics acceleration. 142 | .SH SEE ALSO 143 | .B slop(1) 144 | -------------------------------------------------------------------------------- /src/giph: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | stty -echoctl # don't print ^C when pressing ctrl+c 4 | 5 | readonly VERSION=1.1.1 6 | 7 | # verbosity 8 | VERBOSITY=0 9 | 10 | # options 11 | SLOP=0 12 | DELAY=0 13 | FRAMERATE=20 14 | AUDIO=0 15 | AUDIO_SOURCE=0 16 | MICROPHONE=0 17 | MICROPHONE_SOURCE=default 18 | FORMAT_OVERWRITE="" 19 | 20 | function print_version() { 21 | echo "$VERSION" 22 | exit 0 23 | } 24 | 25 | function print_help() { 26 | cat << EOF 27 | SYNOPSIS 28 | giph [OPTIONS] [FILENAME] 29 | 30 | DESCRIPTION 31 | giph is a screen recorder that records the desktop, a window or selection and encodes it into a video file. 32 | The recorded video file is encoded in one of the supported formats, gif, webm, mp4 or mkv based on the file extension given in [FILENAME]. 33 | When omitting [FILENAME], the default format "gif" is used (use --format to overwrite), and the resulting video directly printed to standard output. 34 | 35 | OPTIONS 36 | -h, --help Print help and exit. 37 | --version Print version and exit. 38 | -v*, --verbose, --quiet Set the verbosity. 39 | -s, --select Enable slop selection. 40 | -g, --geometry=STRING Record rectangle by geometry. (like 100x300+0+0) 41 | -w, --window=INT Record window by id. 42 | -d, --delay=INT Time in seconds before the recording starts. 43 | -t, --timer=TIMEDURATION Time of the recording. (e.g. 10 for 10 seconds or 1:30 for 1 minute 30 seconds) 44 | -f, --framerate=INT Set the framerate. 45 | --format Set the wanted output format. This overwrites the autodetection. 46 | -a, --audio Enable audio recording. 47 | -as, --audio-source=STRING Overwrite the default audio source. 48 | -m, --microphone Enable microphone recording. 49 | -ms, --microphone-source=STRING Overwrite the default microphone source. 50 | -y, --notify Send notification on error or success. 51 | --stop Finish any running giph recordings. 52 | 53 | SLOP OPTIONS 54 | -b, --bordersize=FLOAT Set the selection border thickness. 55 | -p, --padding=FLOAT Set the selection padding. 56 | -to, --tolerance=FLOAT Set how far the mouse can move after clicking before recrangle draws. 57 | -c, --color=FLOAT,FLOAT,FLOAT,FLOAT Set the selection color. 58 | -r, --shader=STRING Set the shader to be used. 59 | -n, --nodecorations=INT Set how aggresively decorations should be avoided. 60 | -l, --highlight Highlight the selection rectangle. 61 | -k, --nokeyboard Disable cancel through keypress. 62 | -o, --noopengl Disable graphics acceleration. 63 | EOF 64 | 65 | exit 0 66 | } 67 | 68 | # log a message - (message:string, verbosity:int, timestamp:bool, stop_execution:bool, no_trailing_newline:bool) 69 | function log() { 70 | [ "${2:-1}" -gt "$VERBOSITY" ] && return 0 71 | 72 | log=(echo -e) 73 | [ "$5" = true ] && log+=(-n) 74 | [ "$3" = true ] && log+=("\033[0;37m$(date '+%Y-%m-%d %H:%M:%S'):\033[0m") 75 | log+=("$1") 76 | "${log[@]}" 77 | 78 | [ "$4" = true ] && exit 1 79 | } 80 | 81 | function log_error() { 82 | notify "$1" "critical" 83 | log "\033[0;31mERROR:\033[0m $1" -1 "${2:-true}" true 84 | } 85 | 86 | function log_warning() { 87 | log "\033[0;33mWARNING:\033[0m $1" 0 "${2:-true}" 88 | } 89 | 90 | function log_success() { 91 | notify "$1" "normal" 92 | log "\033[0;32mSUCCESS:\033[0m $1" 0 "${2:-true}" 93 | } 94 | 95 | function log_info() { 96 | log "\033[0;36mINFO:\033[0m $1" 0 "${2:-true}" 97 | } 98 | 99 | function notify() { 100 | [ "$NOTIFY" = 1 ] && { 101 | notify=(notify-send -t 3000) 102 | notify+=(-u "$2") 103 | notify+=("giph" "$1") 104 | "${notify[@]}" 105 | } 106 | } 107 | 108 | if [ -z "$1" ]; then 109 | print_help 110 | fi 111 | 112 | # flag handling 113 | while [[ "$1" == -* ]]; do 114 | case "$1" in 115 | -h|--help) 116 | print_help 117 | ;; 118 | --version) 119 | print_version 120 | ;; 121 | -v*) 122 | (( VERBOSITY += ${#1} - 1 )) 123 | ;; 124 | --verbose) 125 | (( VERBOSITY++ )) 126 | ;; 127 | --quiet) 128 | VERBOSITY=-1 129 | ;; 130 | --stop) 131 | SHOULD_STOP=true 132 | ;; 133 | -s|--select) 134 | SLOP=1 135 | ;; 136 | -g|--geometry) 137 | shift 138 | GEOMETRY="$1" 139 | ;; 140 | -w|--window) 141 | shift 142 | WINDOW="$1" 143 | ;; 144 | -d|--delay) 145 | shift 146 | DELAY="$1" 147 | ;; 148 | -t|--timer) 149 | shift 150 | TIMER="$1" 151 | ;; 152 | -f|--framerate) 153 | shift 154 | FRAMERATE="$1" 155 | ;; 156 | --format) 157 | shift 158 | FORMAT_OVERWRITE="$1" 159 | ;; 160 | -a|--audio) 161 | AUDIO=1 162 | ;; 163 | -as|--audio-source) 164 | shift 165 | AUDIO=1 166 | AUDIO_SOURCE="$1" 167 | ;; 168 | -m|--microphone) 169 | MICROPHONE=1 170 | ;; 171 | -ms|--microphone-source) 172 | shift 173 | MICROPHONE=1 174 | MICROPHONE_SOURCE="$1" 175 | ;; 176 | -y|--notify) 177 | NOTIFY=1 178 | ;; 179 | -b|--bordersize) 180 | shift 181 | SLOP_BORDERSIZE="$1" 182 | ;; 183 | -p|--padding) 184 | shift 185 | SLOP_PADDING="$1" 186 | ;; 187 | -to|--tolerance) 188 | shift 189 | SLOP_TOLERANCE="$1" 190 | ;; 191 | -c|--color) 192 | shift 193 | SLOP_COLOR="$1" 194 | ;; 195 | -r|--shader) 196 | shift 197 | SLOP_SHADER="$1" 198 | ;; 199 | -n|--nodecorations) 200 | shift 201 | SLOP_NODECORATIONS="$1" 202 | ;; 203 | -l|--highlight) 204 | SLOP_HIGHLIGHT=true 205 | ;; 206 | -k|--nokeyboard) 207 | SLOP_NOKEYBOARD=true 208 | ;; 209 | -o|--noopengl) 210 | SLOP_NOOPENGL=true 211 | ;; 212 | -*) 213 | log_error "option '$1' does not exist" false 214 | ;; 215 | esac 216 | shift 217 | done 218 | 219 | # set verbosity to -1 if the file should be printed to stdout 220 | [ -n "$1" ] && OUTPUT_FILE="$1" || VERBOSITY=-1 221 | 222 | case "$OUTPUT_FILE" in 223 | *.webm) FORMAT="webm" ;; 224 | *.mp4) FORMAT="mp4" ;; 225 | *.mkv) FORMAT="mkv" ;; 226 | *) FORMAT="gif" ;; 227 | esac 228 | 229 | if [ -n "$FORMAT_OVERWRITE" ]; then 230 | case "$FORMAT_OVERWRITE" in 231 | webm|mp4|mkv|gif) FORMAT="$FORMAT_OVERWRITE";; 232 | *) log_error "'$FORMAT_OVERWRITE' is not a supported format." 233 | esac 234 | fi 235 | 236 | function get_geometry() { 237 | if [ "$SLOP" = 1 ]; then 238 | log "using slop to determine recording geometry" 1 true 239 | get_geometry_from_slop 240 | elif [ -n "$GEOMETRY" ]; then 241 | log "using provided geometry string" 1 true 242 | get_geometry_from_string 243 | elif [ -n "$WINDOW" ]; then 244 | log "determining geometry from provided window id" 1 true 245 | get_geometry_from_window_id "$WINDOW" 246 | else 247 | log_warning "no method to provide geometry given, using full desktop geometry instead" 248 | log "determining desktop geometry" 1 true 249 | get_geometry_for_desktop 250 | fi 251 | 252 | log "geometry string: '$GEOMETRY_STRING'" 2 true 253 | parse_geometry_string 254 | } 255 | 256 | function get_geometry_from_slop() { 257 | slop=(slop -f "%g") 258 | [ -n "$SLOP_BORDERSIZE" ] && slop+=(-b "$SLOP_BORDERSIZE") 259 | [ -n "$SLOP_PADDING" ] && slop+=(-p "$SLOP_PADDING") 260 | [ -n "$SLOP_TOLERANCE" ] && slop+=(-t "$SLOP_TOLERANCE") 261 | [ -n "$SLOP_COLOR" ] && slop+=(-c "$SLOP_COLOR") 262 | [ -n "$SLOP_SHADER" ] && slop+=(-r "$SLOP_SHADER") 263 | [ -n "$SLOP_NODECORATIONS" ] && slop+=(-n "$SLOP_NODECORATIONS") 264 | [ -n "$SLOP_HIGHLIGHT" ] && slop+=(-l) 265 | [ -n "$SLOP_NOKEYBOARD" ] && slop+=(-k) 266 | [ -n "$SLOP_NOOPENGL" ] && slop+=(-o) 267 | [ "$VERBOSITY" -lt "3" ] && slop+=(-q) 268 | 269 | slop_command="${slop[*]}" 270 | log "slop command: '$slop_command'" 2 true 271 | 272 | slop_value="$($slop_command)" 273 | [ "$?" -eq 1 ] && log_error "slop selection got canceled" 274 | 275 | GEOMETRY_STRING="$slop_value" 276 | } 277 | 278 | function get_geometry_from_string() { 279 | GEOMETRY_STRING="$GEOMETRY" 280 | } 281 | 282 | function get_geometry_from_window_id() { 283 | xdotool_output="$(xdotool getwindowgeometry --shell "$1" 2> /dev/null)" 284 | [ "$?" = 1 ] && log_error "window with id $1 does not exist" 285 | 286 | WIDTH=0 287 | HEIGHT=0 288 | 289 | eval "$xdotool_output" 290 | 291 | [[ "$X" != "-"* ]] && X="+$X" 292 | [[ "$Y" != "-"* ]] && Y="+$Y" 293 | 294 | GEOMETRY_STRING="${WIDTH}x${HEIGHT}${X}${Y}" 295 | } 296 | 297 | function get_geometry_for_desktop() { 298 | root_window_id="$(xdotool search --maxdepth 0 --class '')" 299 | get_geometry_from_window_id "$root_window_id" 300 | } 301 | 302 | function parse_geometry_string() { 303 | [[ "$GEOMETRY_STRING" =~ ([0-9]+)x([0-9]+)[+]?([-]?[0-9]+)[+]?([-]?[0-9]+) ]] 304 | 305 | width="${BASH_REMATCH[1]}" 306 | height="${BASH_REMATCH[2]}" 307 | x="${BASH_REMATCH[3]}" 308 | y="${BASH_REMATCH[4]}" 309 | 310 | [ -z "$width" ] || [ -z "$height" ] || [ -z "$x" ] || [ -z "$y" ] && { 311 | log_error "could not parse geometry string '$GEOMETRY_STRING'" 312 | } 313 | 314 | get_geometry_for_desktop 315 | 316 | [ "$x" -gt "$WIDTH" ] || [ "$((x + width))" -lt 0 ] || [ "$y" -gt "$HEIGHT" ] || [ "$((y + height))" -lt 0 ] && { 317 | log_error "the area to record is fully outside of the root window" 318 | } 319 | 320 | [ "$x" -lt 0 ] && { 321 | width="$((width + x))" 322 | x="0" 323 | } 324 | 325 | [ "$y" -lt 0 ] && { 326 | height="$((height + y))" 327 | y="0" 328 | } 329 | 330 | [ "$((x+width))" -gt "$WIDTH" ] && { 331 | ((width-="$((x + width - WIDTH))")) 332 | } 333 | 334 | [ "$((y+height))" -gt "$HEIGHT" ] && { 335 | ((height-="$((y + height - HEIGHT))")) 336 | } 337 | 338 | log "parsed geometry: width=$width, height=$height, x=$x, y=$y" 2 true 339 | } 340 | 341 | function create_temporary_directory() { 342 | TEMP_DIRECTORY="$(mktemp -d)" 343 | [ "$?" = 1 ] && log_error "could not create temporary directory" 344 | log "created temporary directory $TEMP_DIRECTORY" 2 true 345 | } 346 | 347 | function record() { 348 | ffmpeg=(ffmpeg -f x11grab -s "$width"x"$height" -i "$DISPLAY+$x,$y") 349 | 350 | if [ "$FORMAT" != "gif" ]; then 351 | [ "$AUDIO" = 1 ] && ffmpeg+=(-f pulse -i "$AUDIO_SOURCE") 352 | [ "$MICROPHONE" = 1 ] && ffmpeg+=(-f pulse -i "$MICROPHONE_SOURCE") 353 | [ "$AUDIO" = 1 ] && [ "$MICROPHONE" = 1 ] && ffmpeg+=(-filter_complex amerge -ac 2) 354 | fi 355 | 356 | ffmpeg+=(-vf "crop=trunc(iw/2)*2:trunc(ih/2)*2") 357 | 358 | [ "$VERBOSITY" -lt "3" ] && ffmpeg+=(-loglevel "quiet") 359 | [ -n "$TIMER" ] && [ "$TIMER" -gt 0 ] && ffmpeg+=(-t "$TIMER") 360 | 361 | ffmpeg+=(-r "$FRAMERATE") 362 | 363 | case "$FORMAT" in 364 | mp4|mkv) 365 | ffmpeg+=(-pix_fmt yuv420p) 366 | ffmpeg+=(-crf 15) 367 | ffmpeg+=("$TEMP_DIRECTORY/final.$FORMAT") 368 | ;; 369 | gif|webm|*) 370 | ffmpeg+=(-preset veryslow) 371 | ffmpeg+=(-crf 0) 372 | ffmpeg+=("$TEMP_DIRECTORY/lossless.mp4") 373 | ;; 374 | esac 375 | 376 | [ -n "$DELAY" ] && [ "$DELAY" -gt 0 ] && countdown_cli "$DELAY" "recording starts in" 377 | 378 | ffmpeg_command="${ffmpeg[*]}" 379 | log "ffmpeg command: '$ffmpeg_command'" 2 true 380 | 381 | "${ffmpeg[@]}" & 382 | FFMPEG_PID="$!" 383 | 384 | log "started recording video with ffmpeg" 1 true 385 | 386 | if [ -n "$TIMER" ] && [ "$TIMER" -gt 0 ]; then 387 | countdown_cli "$TIMER" "recording stops in" 388 | else 389 | stop_recording_handler_cli 390 | fi 391 | 392 | wait "$FFMPEG_PID" 393 | 394 | [ "$?" = 1 ] && log_error "recording video with ffmpeg failed" 395 | log "completed ffmpeg video recording" 1 true 396 | } 397 | 398 | function countdown_cli() { 399 | seconds="$1" 400 | while [ "$seconds" -ge 0 ]; do 401 | log "\r\033[K\033[0;36m$2:\033[0m $seconds" 0 false false true 402 | if [ "$seconds" -gt 0 ]; then 403 | sleep 1 404 | else 405 | log "\r\033[K" 0 false false true 406 | fi 407 | : "$((seconds--))" 408 | done 409 | } 410 | 411 | function stop_recording_handler_cli() { 412 | log_info "stop recording with \033[1;36mctrl+c\033[0m or call \033[1;36mgiph --stop\033[0m" 413 | trap '' INT 414 | } 415 | 416 | function encode() { 417 | log "encoding $FORMAT using ffmpeg" 1 true 418 | 419 | ffmpeg_encode=(ffmpeg -i "$TEMP_DIRECTORY/lossless.mp4") 420 | 421 | case "$FORMAT" in 422 | "gif") 423 | ffmpeg_encode+=(-vf "split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse") 424 | ;; 425 | "webm") 426 | ffmpeg_encode+=(-c:v libvpx-vp9) 427 | ;; 428 | esac 429 | 430 | [ "$VERBOSITY" -lt "3" ] && ffmpeg_encode+=(-loglevel "quiet") 431 | 432 | ffmpeg_encode+=("$TEMP_DIRECTORY/final.$FORMAT") 433 | 434 | ffmpeg_encode_command="${ffmpeg_encode[*]}" 435 | log "ffmpeg encode command: '$ffmpeg_encode_command'" 2 true 436 | 437 | "${ffmpeg_encode[@]}" 438 | [ "$?" = 1 ] && log_error "could not encode $FORMAT from lossless recording" 439 | } 440 | 441 | function deliver() { 442 | if [ -n "$OUTPUT_FILE" ]; then 443 | mv "$1" "$OUTPUT_FILE" && { 444 | log_success "final $FORMAT saved as \"$OUTPUT_FILE\"" 445 | } 446 | else 447 | cat "$1" 448 | fi 449 | } 450 | 451 | function delete_temporary_directory() { 452 | rm -r "$TEMP_DIRECTORY" 453 | [ "$?" = 1 ] && log_error "could not delete temporary directory" 454 | log "deleted temporary directory $TEMP_DIRECTORY" 2 true 455 | } 456 | 457 | function giph() { 458 | get_geometry 459 | create_temporary_directory 460 | record 461 | 462 | if [[ "$FORMAT" == "gif" || "$FORMAT" == "webm" ]]; then 463 | encode 464 | fi 465 | 466 | deliver "$TEMP_DIRECTORY/final.$FORMAT" 467 | 468 | delete_temporary_directory 469 | exit 0 470 | } 471 | 472 | function stop_all_recordings() { 473 | for pid in "$(pgrep -f "bash.+giph")"; do 474 | kill -INT -$pid 475 | done 476 | } 477 | 478 | if [ -n "$SHOULD_STOP" ]; then 479 | stop_all_recordings 480 | else 481 | giph 482 | fi 483 | 484 | wait 485 | --------------------------------------------------------------------------------