Skip to main content

asciinema & asciicast2gif

Sometimes a help text to convey how to properly use a command, or a help text exists but people are not reading it. In these cases a short video might do the trick. asciinema is quite popular and allows to record terminal sessions and replay them later on. But to embed these recordings in a web page another tool was needed: asciicast2gif.

asciinema was easy to install as its available in many distributions:

sudo dnf install asciinema


Record sessions with rec, play sessions with play, easy enough.

asciicast2gif wasn't available in our distribution and needed to be installed manually:

npm install asciicast2gif
sudo dnf install ImageMagick gifsicle


The first few tries failed with a horrid error message, so let's see what asciicast2gif really does:

$ asciicast2gif /tmp/test.cast /tmp/test.gif
[...]
convert -loop 0 -delay 20  /tmp/tmp.UccOho2kuG/0.png \
                -delay 420 /tmp/tmp.UccOho2kuG/1.png \
                -delay 86  /tmp/tmp.UccOho2kuG/2.png \
          [...] -layers Optimize gif:- | gifsicle -k 64 -O2 -Okeep-empty --lossy=80 -o /tmp/test.gif


And looking in dmesg revealed that convert was running out of memory while trying to convert a ~30 second screen cast recording! After granting the machine a bit more memory, the command completed and we now had a working GIF to embed in our web page:

$ asciicast2gif -t tango test.cast test.gif
==> Loading test.cast...
==> Spawning PhantomJS renderer...
==> Generating frame screenshots...
==> Combining 137 screenshots into GIF file...
gifsicle: warning: huge GIF, conserving memory (processing may take a while)
==> Done.

$ mv test.gif ~/www/
$ cat ~/www/test.md
[...]
[video](test.gif)


Note: limiting the memory usage of the node process did not seem to work, as can be seen in top:

Tasks: 117 total,   2 running, 115 sleeping,   0 stopped,   0 zombie
%Cpu0  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  2.0 us,  0.0 sy,  0.0 ni, 74.0 id, 24.0 wa,  0.0 hi,  0.0 si,  0.0 st
GiB Mem :      7.8 total,      1.7 free,      6.0 used,      0.1 buff/cache
GiB Swap:      0.1 total,      0.0 free,      0.1 used.      1.6 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   1971 dummy     20   0 5996.9m   5.8g   1.5m R 103.0  75.0   1:05.03 convert -loop 0 -delay 13 [...]
   1958 dummy     20   0  635.3m  30.7m   0.2m S   0.0   0.4   0:05.03 node --max-old-space-size=512 [...]


The actual gif quality varies depending on what's shown on the terminal. Movie like sequences look a bit crappy though, but that's not what we wanted to do in the first place, hm? :-) test.gif


error message

gifsicle:<stdin>: empty file

    at checkExecSyncError (child_process.js:630:11)
    at Object.execSync (child_process.js:666:15)
    at Dp (/home/dummy/node_modules/asciicast2gif/main.js:708:246)
    at /home/dummy/node_modules/asciicast2gif/main.js:713:178
    at Function.b [as h] (/home/dummy/node_modules/asciicast2gif/main.js:709:287)
    at ep (/home/dummy/node_modules/asciicast2gif/main.js:697:48)
    at /home/dummy/node_modules/asciicast2gif/main.js:697:193
    at /home/dummy/node_modules/asciicast2gif/main.js:689:264
    at Immediate.Po (/home/dummy/node_modules/asciicast2gif/main.js:685:331)
    at processImmediate (internal/timers.js:456:21) {
  status: 1,
  signal: null,
  output: [
    null,
    Buffer(0) [Uint8Array] [],
    Buffer(29) [Uint8Array] [
      103, 105, 102, 115, 105,  99, 108,
      101,  58,  60, 115, 116, 100, 105,
      110,  62,  58,  32, 101, 109, 112,
      116, 121,  32, 102, 105, 108, 101,
       10
    ]
  ],
  pid: 2836,   
  stdout: Buffer(0) [Uint8Array] [],
  stderr: Buffer(29) [Uint8Array] [
    103, 105, 102, 115, 105,  99, 108,
    101,  58,  60, 115, 116, 100, 105,
    110,  62,  58,  32, 101, 109, 112,
    116, 121,  32, 102, 105, 108, 101,
     10
  ]
}