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:

sudo dnf install asciinema

Record sessions with asciinema rec, play sessions with asciinema play, cool.

Update: asciicast2gif is no longer maintained and agg appears to be the successor, and is much faster than asciicast2gif. Once all this was installed, generating GIFs was easy enough. Stitching both tools togeter:

$ asciinema rec demo.cast
timeout 5 asciiquarium
^D

$ agg demo.cast demo.gif

Et voilà:

asciinema_demo.gif

For posterity, let's leave these old notes in place:

$ 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
  ]
}