65

To take a static screenshot of a selected part of my screen, I often use scrot with -s shot.png. This is great for adding illustrations to StackExchange posts. I even found this script to automatically upload such a screenshot to Imgur.com and put a link in my X clipboard!

Let's turn this up to twelve: How do I similarly create a GIF screencast?

There are programs like recordmydesktop, byzanz & co as discussed on Ask Ubuntu that aim to be "user friendly", but in my experience are buggy, inefficient, mostly unscriptable and unsuited for little one-off things like this.

I just want to select an area and record a GIF, with a console command I can understand, not some arcane unscriptable GUI monstrosity.

How can I do this?

2

7 Answers 7

77

OK then

GIF vimcast!

I started ffcast, did vim, quit ffcast, then converted .avi.gif.

I ran the recording commands in another terminal. Polished script for your $PATH at the end of this answer.

What happened?

Capturing

FFcast helps the user interactively select a screen region and hands over the geometry to an external command, such as FFmpeg, for screen recording.

ffcast is the glorious product of some hacking at the Arch Linux community (mainly lolilolicon). You can find it on github (or in the AUR for Archers). Its dependency list is just bash and ffmpeg, though you'll want xrectsel (AUR link) for interactive rectangle selection.

You can also append ffmpeg flags right after the command. I set -r 15 to capture at 15 frames per second and -codec:v huffyuv for lossless recording. (Play with these to tweak the size/quality tradeoff.)

GIFfing

ImageMagick can read .avi videos and has some GIF optimisation tricks that drastically reduce file size while preserving quality: The -layers Optimize to convert invokes the general-purpose optimiser. The ImageMagick manual has a page on advanced optimisations too.

Final script

This is what I have in my $PATH. It records into a temporary file before converting.

#!/bin/bash
TMP_AVI=$(mktemp /tmp/outXXXXXXXXXX.avi)
ffcast -s % ffmpeg -y -f x11grab -show_region 1 -framerate 15 \
    -video_size %s -i %D+%c -codec:v huffyuv                  \
    -vf crop="iw-mod(iw\\,2):ih-mod(ih\\,2)" $TMP_AVI         \
&& convert -set delay 10 -layers Optimize $TMP_AVI out.gif

Thanks to BenC for detective work in figuring out the correct flags after the recent ffcast update.

If you'd like to install the dependencies on a Debian-based distro, Louis has written helpful installation notes.

Wheeeeee!

10
  • 2
    I've created a version who doesn't require bash, but who works on any POSIX compliant shell github.com/chilicuil/ffcast Mar 22, 2014 at 4:31
  • 2
    Looks like the command line syntax of ffcast has changed: github.com/lolilolicon/FFcast/issues/8 Sep 30, 2014 at 20:25
  • 3
    The simplest way to do it is now ffcast -s rec [filename], though that doesn't give the exact settings you're using in your example. Unfortunately, to give exact settings, you now have to give the entire ffmpeg command. Your call about the best way to update this answer :) Oct 1, 2014 at 1:47
  • 5
    Based on the comments on GH, ffcast -s % ffmpeg -f x11grab -show_region 1 -framerate 20 -video_size %s -i %D+%c -codec:v huffyuv -vf crop="iw-mod(iw\\,2):ih-mod(ih\\,2)" $TMP_AVI seems to do the trick.
    – BenC
    Oct 1, 2014 at 10:35
  • 1
    Some notes on installation of some of these things for anyone else without these already on their system Mar 24, 2015 at 22:21
13

For me, the answer was to use ffcast with ffmpeg like so:

ffcast -w % ffmpeg -f x11grab -show_region 1 -framerate 20 -video_size %s -i %D+%c -codec:v huffyuv -vf crop="iw-mod(iw\\,2):ih-mod(ih\\,2)" out.avi

I then used ffmpeg to do the conversion from avi to gif - it's very fast and it keeps the framerate intact:

ffmpeg -i out.avi -pix_fmt rgb24 out.gif

Lastly I used convert in the same way as @anko's answer to optimise the gif, but I set a limit on resource usage to stop convert exiting with a killed message, and I removed the delay as ffmpeg has already handled that:

convert -limit memory 1 -limit map 1 -layers Optimize out.gif out_optimised.gif
5

for my setup(ubuntu 16.04), ffcast doesn't work well as it's not updated on github for quite a while.

so I put up a script using slop(https://github.com/naelstrof/slop) and ffmpeg.

an example:

yay it's working

#!/bin/bash

read -r X Y W H G ID < <(slop -f "%x %y %w %h %g %i")
TMP_AVI=$(mktemp /tmp/outXXXXXXXXXX.avi)

ffmpeg -s "$W"x"$H" -y -f x11grab -i :0.0+$X,$Y -vcodec 
huffyuv -r 25 $TMP_AVI 

convert -set delay 5 -layers Optimize $TMP_AVI out.gif 

Update: Code updated, ffmpeg returns exit code 2 that prevents convert from executing as mentioned by Rub. After recording, hit ctrl+C will exit ffmpeg and run convert to generate the out.gif.

3

I had wrote an interactive wrapper script for unix desktops for this reason, and after a year of usage, i am happy to share it there!

Made with byzanz, gifsicle, xdotool, and the script is written in php.

Example output:

[1020px, not resized gif width 1020px, 70 seconds, 50 colors, 65Kb]

enter image description here

It provides good compressed gifs,and is a good showcase for this question.

This is a fairly simple base, ready to be hacked by you.

Functionalities: Gif record at mouse positions or fullscreen, resizing, compression, color compression, reverse/merge, giphy.com curl upload.

To start a 10 seconds gif record: gif 10

To record multiple times with the same parameters: gif !

To start a fullscreen 5 second gif record: gif 5 --fullscreen

Script runnning, pleasantly recording himself:

[45 seconds, width 645px, full colors, 976kb]
enter image description here

Full 5kb script:

#!/usr/bin/php

<?php
#> php xdotool byzanz gifsicle curl
#@ https://webdev23.github.io/gif/gif

echo "Usage: ./gif [time in seconds|!] [--fullscreen|-f]\n";
echo "--------------------------------------------------\n";
echo "Gif recorder tool\n";
echo "gif ! to call back last settings\n";
echo "Please move your mouse at the top left corner point\n";
echo "of the wanted gif area. Then press enter.\n";
echo "\n";

#~ Nico KraZhtest | 05/2017 | https://github.com/webdev23/gif
#~ Create fluid GIF's fastly
#~ You can set the gif record time as argument: ./gif 10
#~ Default record time is 1 seconde, or set it now:
   $recordTime = 1;
#~ ----------------

$t = @$argv[1];

$x1;$y1;$x2;$y2;$gw;$gh;$defc;$rw;

if (!isset($argv[1]) || @$argv[1] === "!") {
  $t = $recordTime;
}

if (@$argv[1] === "!") {
  $pos = file_get_contents("./.config/gif/pos");
  $pos = explode("\n", $pos);
  $x1 = $pos[0];
  $y1 = $pos[1];
  $x2 = $pos[2];
  $y2 = $pos[3];
  $gw = $pos[4];
  $gh = $pos[5];
  $t = $pos[6];
  @$GLOBALS['defc'] = $pos[7];
  @$GLOBALS['$rw'] = $pos[8];
   #~ echo $x1." ".$y1." ".$x2." ".$y2." ".$gw." ".$gh." ".$t." ".$defc." ".@$rw;
  }

else if (@$argv[2] === "fullscreen" || @$argv[2] === "--fullscreen" || @$argv[2] === "-f" || @$argv[2] === "f") {
  echo "############\nStarting fullscreen record\n";
  $fs = system("xdpyinfo  | grep 'dimensions:'");
  echo "\n";
  $fs = explode("    ",$fs);
  echo $fs[1];
  $fs = explode(" ",$fs[1]);
  echo $fs[0];
  $fs = explode("x",$fs[0]);
  echo $fs[0]."\n";
  echo $fs[1];
  $x1 = "0";
  $y1 = "0";
  $x2 = "fs";
  $y2 = "fs";
  $gw = $fs[0];
  $gh = $fs[1];
  $t = $argv[1];
  system("mkdir -p ./.config/gif/");
  system("cd ./.config/gif/ && \
          echo '$x1\n$y1\n$x2\n$y2\n$gw\n$gh\n$t\n\n\n\n' > pos");
  }

else {
  $stdin = fopen('php://stdin', 'r');
  $response = rtrim(fgets(STDIN));

  $p1 = system("xdotool getmouselocation");

  $pos1 = explode(" ",$p1);

  $x1 = $pos1[0];
  $x1 = explode(":",$x1);
  $x1 = $x1[1];
  echo "X1: ".$x1;

  $y1 = $pos1[1];
  $y1 = explode(":",$y1);
  $y1 = $y1[1];
  echo " Y1: ".$y1;

  echo "\nNow move your mousse at the bottom right corner.\nThen enter\n";

  $stdin = fopen('php://stdin', 'r');
  $response = rtrim(fgets(STDIN));

  $p2 = system("xdotool getmouselocation");

  $pos2 = explode(" ",$p2);

  $x2 = $pos2[0];
  $x2 = explode(":",$x2);
  $x2 = $x2[1];
  echo "X2: ".$x2;

  $y2 = $pos2[1];
  $y2 = explode(":",$y2);
  $y2 = $y2[1];
  echo " Y2: ".$y2;

  $gw = ($x2 - $x1);
  echo "\nGif width: ".$gw;

  $gh = ($y2 - $y1);
  echo "\nGif height: ".$gh;
  echo "\n".$x1." ".$y1." ".$x2." ".$y2." ".$gw." ".$gh." ".$t."\n";

  system("mkdir -p ./.config/gif/");
  system("cd ./.config/gif/ && \
          echo '$x1\n$y1\n$x2\n$y2\n$gw\n$gh\n$t\n\n\n\n' > pos");
  }

$unix = date_timestamp_get(date_create());

echo "\n".$unix." | Starting ".$t."s gif record\n";

@system("byzanz-record \
        -v             \
        --duration=$t  \
        --x=$x1        \
        --y=$y1        \
        --width=$gw    \
        --height=$gh   \
        ~/Pictures/gif$unix.gif");

$named = "gif".$unix;

echo "Saved as ~/Pictures/".$named.".gif\n";

echo "\nOptimize | How many colors to keep? (default 100, max 256) \n";

if (@$argv[1] === "!"){
  $pos = file_get_contents("./.config/gif/pos");
  $pos = explode("\n", $pos);
  $defc = $pos[7];
  }

if (!isset($defc)){
  $defc = readline("Colors: ");
  }

if (empty($defc)){
  $defc = "100";
  }

echo "\nKeeping ".$defc." colors\n";

system("gifsicle --verbose -i ~/Pictures/$named.gif -O5 --colors=$defc -o ~/Pictures/$named\_reduced.gif");

echo "\nOptimize | Resize width in pixels (default 360px) \n";

if (@$argv[1] === "!"){
  $pos = file_get_contents("./.config/gif/pos");
  $pos = explode("\n", $pos);
  $rw = $pos[8];
  }

if (!isset($rw)){
  $rw = readline("Width : ");
  }

if (empty($rw)){
  $rw = "360";
  }

echo "\nResized by ".$rw." pixels width\n";

@system("gifsicle --verbose -i ~/Pictures/$named\_reduced.gif --resize-width $rw -o ~/Pictures/".$named."_optimized.gif");

$opt = "~/Pictures/".$named."_optimized.gif";

usleep(5000000);

echo "\nSpecial | Reverse and merge?\n";

system("xdg-open ~/Pictures/".$named."_optimized.gif > /dev/null");

if (@$argv[1] === "!"){
  $pos = file_get_contents("./.config/gif/pos");
  $pos = explode("\n", $pos);
  $rev = $pos[9];
  }

if (!isset($rev)){
  $stdin = fopen('php://stdin', 'r');
  $rev = rtrim(fgets(STDIN));
  $rev = "1";
  }

if (!isset($rev)){
  $rev = "0";
  }

@system("cd ./.config/gif/ && sed -i '8s/.*/$defc/' pos");
@system("cd ./.config/gif/ && sed -i '9s/.*/$rw/' pos");
@system("cd ./.config/gif/ && sed -i '10s/.*/$rev/' pos");

if ($rev === "1"){
  @system("gifsicle                           \
            -i ~/Pictures/$named\_reduced.gif \
            '#-2-1'                           \
            -o ~/Pictures/".$named."_reversed.gif");

  $inv = "~/Pictures/".$named."_reversed.gif";

  usleep(400000);

  @system("gifsicle                           \
            -i ~/Pictures/$named\_reduced.gif \
          --append $inv                       \
          --resize-width $rw                  \
          -o ~/Pictures/".$named."_merged.gif");

  usleep(3000000);

  system("xdg-open ~/Pictures/".$named."_merged.gif > /dev/null");

  }

echo "\n####################";
echo "\nUpload to giphy.com?\n";

$stdin = fopen('php://stdin', 'r');
$response = rtrim(fgets(STDIN));

$m = "~/Pictures/".$named."_merged.gif";
$f = system("du -h $m");
$f = explode("  ",$f);
$f = $f[1];

$www = system('curl                         \
                --progress-bar              \
                -v                          \
                -F "file=@'.$f.'"           \
                -F "api_key=dc6zaTOxFJmzC"  \
                "http://upload.giphy.com/v1/gifs"');

$www = json_decode($www);

echo "\n\nhttps://i.giphy.com/".$www->data->id.".gif\n";

echo "\nThanks YOU!\n";

Reverse/merge capability, to create artistic stuffs.

Original (435kb)

enter image description here

Reversed, merged: (826kb)

enter image description here

To install, using phi:

php <(curl https://webdev23.github.io/phi/phi) install https://webdev23.github.io/gif/gif

Fullscreen:

[1920*1080px, gif 400px, 50 seconds, 100 colors, 2Mb]

enter image description here

Source, with some more explanation and potential updates: https://github.com/webdev23/gif

2

This repository help you to create your gif from selection region and also optimize it for you

https://github.com/devlifeX/record-screen-gif

1

Building on top of TC Zhang's answer (I liked the simplicity, but couldn't see how to to stop it), and taking gifsicle from NVRM's answer (very nice quality I must say), I created my own version.

It starts by asking for the area to record (silently) and then a little box with buttons is placed just under the lower right corner of your selection (not interfering with the recording. It stays on top, but without grabbing focus.

You can pause/continue or just Stop, anytime. I consider a pain to have to specify the duration in advance, be free!

Result is placed in ~/Downloads/ with a timestamp like 2020-02-29--0237.gif (I prefer not to spend time specifying where/how to save).

Feel free to customize for your needs. I tried to make it very self explanatory.

I learned a bunch putting it all together (yad, ffmpeg and signals, bash, etc.)

If someone finds better parameters for the recording/conversion quality please let me know. I am happy to update it.

Regards, me.

Example:

enter image description here

#!/bin/bash

# Inspired on script from https://unix.stackexchange.com/questions/113695/gif-screencasting-the-unix-way

# Failsafe, just in case interactive stop fails, recording can be large
# If you are goint to pause consider increasing it as clock is still ticking while paused
DURATION=40;

# Destination for output gif file
GIF_OUT_FOLDER="/home/$USER/Downloads"
GIF_OUT_FILE=$GIF_OUT_FOLDER/$(date  +'%Y-%m-%d--%H%M').gif

# Request section to record interactively
read -r X Y W H G ID < <(slop -f "%x %y %w %h %g %i")

# Create the name for the temp file with video
TMP_AVI=$(mktemp /tmp/outXXXXXXXXXX.avi)

# Export variables
export TMP_AVI DURATION GIF_OUT_FOLDER GIF_OUT_FILE X Y W H G ID

# Define functions and export them
#--------------------------------------------
stop_recording_and_create_gif() {
killall --user $USER  --ignore-case  --signal INT  ffmpeg ; convert  -layers removeDups -layers Optimize -delay 13 -loop 0 $TMP_AVI  $GIF_OUT_FILE && rm -I "$TMP_AVI";
# Optimize gif file
gifsicle --verbose --batch --interlace $GIF_OUT_FILE --optimize=05 --colors=128; # --output  out.gif
}
export -f stop_recording_and_create_gif
#--------------------------------------------
start_recording_screen_section () {
# Record and convert to gif file
#3 not-original / added -t n   for n seconds recording
ffmpeg -t $DURATION -s "$W"x"$H" -y -f x11grab -i :0.0+$X,$Y -vcodec huffyuv -r 25 $TMP_AVI;
}
export -f start_recording_screen_section
#--------------------------------------------
FContinue () { killall --user $USER  --ignore-case  --signal SIGCONT ffmpeg ;}
FPause () { killall --user $USER  --ignore-case  --signal SIGSTOP ffmpeg ; }

export -f FContinue FPause
####################################

# Collect the YAD options
cmd=(yad 
    --width 5
    --height 5
    --on-top
    --skip-taskbar
    --borders=0
    #--undecorated  (so you have something to grab it if you want to move it)
    --columns 1
    --no-focus
    #--mouse (it gets a bit inside the recording window)
    --geometry=5x5+$(($X+$W+10))+$(($Y+$H+10))
    #--no-buttons  (avoids having buttons we don't need)
    # This is a trick to have the buttons vertical
    --form  
    --title="Screencast to GIF"
    --field='Start recording!stop!Start recording (maximum defined duration)':fbtn 'bash -c "start_recording_screen_section"'
    --field='Stop recording!gtk-quit!Stop recording and create gif file in output folder':fbtn 'bash -c "stop_recording_and_create_gif & kill -SIGUSR1 $YAD_PID"'
    --field='Pause!gtk-pause!':fbtn 'bash -c "FPause"'
    --field='Continue!gtk-continue!':fbtn 'bash -c "FContinue"'
    --button='Quit!gtk-ok':'bash -c "kill -SIGUSR1 $YAD_PID"'
)
# Run yad
"${cmd[@]}"

# Cleanup before leaving
unset TMP_AVI DURATION GIF_OUT_FOLDER GIF_OUT_FILE X Y W H G ID
unset stop_recording_and_create_gif
unset start_recording_screen_section
unset FContinue FPause
1
  • In my script, use ctrl+C will stop the recording and generate the gif. The code in my old answer won't work because ffmpeg returns exit code 2, which prevents convert from executing. Thanks for the reminder!
    – TC Zhang
    Mar 2, 2020 at 7:34
0

I examined ImageMagick's import and it turns out that the output contains the dimensions of the captured area. So, if you're like me and you have ImageMagick installed and don't want to install another utility just for getting the resolution and x/y offset of the selected area, you may use something like this:

#!/bin/sh

eval "$(
    import - | awk '
    /^DisplayImage$/{
        getline
        print "X=" $1
        print "Y=" $2
        getline
        print "W=" $1
        print "H=" $2
        exit
        }
'
)"

# Record a region of your desktop as gif
ffmpeg -f x11grab -s "$W"x"$H" -r 30 -i :0.0+"$X","$Y" -pix_fmt rgb24 -loop 0 output.gif

# Take a partial screenshot using ffmpeg
# ffmpeg -f x11grab -s "$W"x"$H" -i :0.0+"$X","$Y" -vframes 1 output.png
# You might as well just use `import` (`import` doesn't support gifs though)
# import output.png

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .