DEV Community

Cover image for CLI - GIF recording with Ruby and SCROT
decentralizuj
decentralizuj

Posted on • Edited on

CLI - GIF recording with Ruby and SCROT

Preview GIF created with ScreenScrot:

Preview recorded with screenscrot

Take a look about Moriarty on GitHub:

GitHub logo decentralizuj / moriarty

Moriarty - Tool to check social networks for available username

Open-Source Development need donations, in my case that's link/repo sharing, instead of Bitcoin. Comments, discussions and opinions are highly appreciated (good or bad).


Introduction

GIF recording is great to show your friends how the script work. I was looking for an easy way to capture my script output into GIF, but somehow I couldn't find anything that suit my needs. I decided to write few lines of code, and to make it work just like I want.

First, I want to capture screenshots from my terminal, and to have it saved in PNG, so I can use those photos as background or something. I want to start it from terminal, and to record only active window, but with ability to record everything sometimes (or to select with mouse). At the end, I want to enter for how long to capture screenshots, before gif creation. Let's start!

Install SCROT and ImageMagick

On ubuntu-like linux:

sudo apt update -qq sudo apt install scrot imagemagick -y 
Enter fullscreen mode Exit fullscreen mode

Now it's time to write some ruby code to do what we want. Create file called 'screenscrot.rb'.

Create class ScreenScrot

Create class, define constants and make initializer accept args or use those constants.

# capture screenshots on linux with ruby and scrot # save photos as .png # create gif with imagemagick class ScreenScrot TITLE = 'screenscrot' # name of file and folder to create PATH = Dir.pwd # define path to root directory EXT = :png # screenshots extension attr_reader :title, :ext, :path, :directory, :c def initialize( title = TITLE, path = PATH, ext = EXT ) @c = 0 @path, @ext, @title = path.to_s, ext.to_s, title.to_s @path += '/' unless @path.end_with?('/') @filename = @title + '_' + rand(999).to_s @directory = @path + @filename + '/' system "mkdir -p #{@directory}" unless Dir.exist?(@directory) Dir.chdir(@directory) end end 
Enter fullscreen mode Exit fullscreen mode

Here, TITLE is constant to use if parameter title is not defined. Path is path to the working directory, and extension is for captured screenshots. Our new object accept all those parameters, or will use this ones if not defined. After title I added random number, so I do not overwrite past screenshots if I want to record it again (so if I use 'recorder', it will become 'recorder_456'. That is saved in variable @filename. We add this filename to path, together with '/' at the end, and we get @directory variable, which is path where we will save our screenshots and gif at the end). If directory not exist, it will be created and entered.

Capture screenshots

This method is here to save .png screenshot that we will use later with imagemagick.

 # record active window # use :all to capture everything # use :select to capture select rec window # >> @screen.capture(:all) def capture( win = :active ) case win.to_sym when :all then scr = '' # rec everything when :select then scr = '-s' # select with mouse else scr = '-u' # active window (default) end # without name scrot add timestamp, but that is too slow # if use .to_i we get 1 screenshot per second only # so we use .to_f and then remove '.' from name screenshot = @directory + Time.now.to_f.to_s.gsub!('.', '') # prepend command for scrot, add counter to the name # so we can save all frames without problems exec = "scrot #{scr} -q 10 #{screenshot + @c.to_s}.#{@ext}" `exec` @c += 1 # captured frames counter end 
Enter fullscreen mode Exit fullscreen mode

With this we have option to save screenshots into folder, with chosen quality, extension and part of screen to save. Default is active window, so part of screen that is active when recording start. If you change terminal, do it on same part of screen. Otherwise use :select, so you click with mouse on window you want to record. Last option is :all, that will record entire screen.

Convert into GIF

After getting screenshots, we will use ImageMagick's convert to create animated GIF.

 def to_gif( name = nil, opts = {} ) # if name param is nil or '' use default title name = @filename if name.empty? @gif = name.to_s # accept options hash or use default values opts[:dir] ||= @directory opts[:ext] ||= @ext opts[:delay] ||= 20 opts[:loop] ||= 2 Dir.chdir(opts[:dir]) # add .gif to the filename, if do not contain  @gif += '.gif' unless @gif.end_with? '.gif' # construct ImageMagick's convert command from parameters exec = "convert -delay #{opts[:delay]} -loop #{opts[:loop]} *.#{opts[:ext]} #{@gif}" # create GIF `exec` end 
Enter fullscreen mode Exit fullscreen mode

That's it! Method #to_gif accept filename and options hash as parameters, or will use default values. Default filename is @title, and delay is pause in microseconds between two frames. I will add some sleep between capturing screenshots because I do not need all those frames for terminal recording. Loop param define how many times to repeat animation. Zero is infinite loop, but all other values will be increased by one (if loop is 1, it's played once. If loop is 2, animation is played and repeated two more times, which is 3 times at all).

GIF created this way is high quality, even when png quality in scrot command is set to 10 (100 is max). This lead to pretty big file, but still it's ok for short videos. This can be fixed with more arguments in convert command.

Run script

To run everything, I just made it simple. Accept time to record, gif name and part of screen to record as arguments. First argument is time to record, default is 60. Second is name, default is screenscrot. Third arg is part of screen, default is active (that's why script count 5 seconds, so you can activate window to rec).

 # enter name as second argument or use hardcoded TITLE puts "Start Recording Screenshots..." name = ARGV[1] or ScreenScrot::TITLE # initialize new object with name @screen = ScreenScrot.new name # clean terminal and count 5 seconds before start system 'clear' cnt = 5 cnt.times do puts "Starting in #{cnt -= 1}" sleep 1 system 'clear' end puts "[Screenshot Recording Started]" str_time = Time.now # add recording time as first argument, in seconds # default is 60 max_time = ARGV.empty? ? 60 : ARGV[0].to_i # check args for window recording type # if contain --all or --select, use it, else use :active display = :active display = :select if desplay.include?('--select') display = :all if display.include?('--all') # save screenshots untill time elapsed # add small sleep because we record terminal until (Time.now - str_time).to_i >= max_time do @screen.capture(display) # edit or remove sleep, per use-case sleep 0.2 end # notify about end, print time in minutes and number of frames print "[Screenshot Recording Stopped] after " puts "#{(Time.now - str_time).to_f.round(2)} seconds" # at the end, create GIF animation puts "\n [GIF!] - Start creating gif animation:" puts " Name: #{@screen.gif}" puts " Path: #{@screen.directory}\n" @screen.to_gif # notify user about end, print file name and directory print "[CREATED!] - #{@screen.c.to_s} frame" print "s" unless @screen.c.to_s.end_with?('1') puts "collected into GIF animation." puts 
Enter fullscreen mode Exit fullscreen mode

With this you can make nice gif animation of terminal, for showcase or to record tests...

ruby screenscrot.rb 60 myfilename --all 
Enter fullscreen mode Exit fullscreen mode

Full Script

Top comments (0)