Terminal recording on the OrangePi Zero

Terminal recording on the OrangePi Zero

Terminal recording on the OrangePi Zero

Lately, I started writing a few articles on the OrangePi Zero, as I am trying to use it as part of a conference recording tool and to reference what I know about this board on the OrangePi Community docs website.

Of course, I have to show my command lines, and their results.

I used to post my commands on gist, then use Carbon to generate an image with the commands and their outputs… But that’s quite a lot of boring work…

I then discovered asciinema and the recording that can be transformed into a GIF file thanks to asciicast2gif.

It worked pretty well under Ubuntu for my previous projects, but it was not really practical to launch it, issue an ssh command, and then exit twice before having the recording… and modifying it to get rid of the SSH command.

Furthermore, GIF are not usable for visually impaired people, so using something like SVG where they could copy and paste would be better in my opinion…

Unfortunately, SVG is not handled by medium, so it will have to be used on other media, like my Ghost instance (not available yet).

Enters termtosvg. That’s a wonderful tool aimed at producing animated SVG after recording terminal sessions.

It compiled like a breeze (well, a strong, smelly and cold breeze) on the Zero, so I used it quite intensively (before discovering it could not be used on Medium).

I then searched for tools able to transform an animated SVG to GIF, so that I would not have to redo all my terminal recordings… and was not able to do so.

There is one library called librsvg that could be used by ffmpeg, but I have not been able to compile it on the Zero, and it’s just an hypothesis, there is no guarantee at all that it could be used to produce GIF files from animated SVG files.

termtosvg can produce files than can be used by asciinema and then asciicast2gif to produce gif files, but the process is cumbersome and does not work on the Zero. I had to use a X86 Linux machine to do it.

Anyway, with this article, you won’t be able to produce GIF images starting from a terminal recording if you’re using exclusively a Zero, but beautiful animated SVG that you will be able to use on your blog, or anywhere else where animated SVG is supported.

If you’re brave enough to also use an X86 machine, go to the end of this article, and take time to get married, get children before getting your first GIF file.

TermToSVG installation

TL;DR

git config --global http.proxy http://proxy-machine:proxy-port
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo nano /etc/fstab
git clone https://github.com/nbedos/termtosvg.git
sudo apt-get install python3-setuptools libxml2-dev libxslt-dev python3-dev python3-pip
pip3 install wheel
cd termtosvg
echo "Let's modify the Makefile so that we use python3 and pip3 as I can't get rid of Python2.7.x on this machine..."
echo "See at https://gist.github.com/gounthar/b1870915710a0437b4e4eb8690e93553#file-makefile-patch"
make build
pip3 install dist/termtosvg-1.1.0-py3-none-any.whl

The real installation process

Let’s try to install termtosvg on the Zero. First of all, let’s configure the proxy if needed and then get the source code:

poddingue@orangepizero:~$ git clone https://github.com/nbedos/termtosvg.git
Cloning into 'termtosvg'...
remote: Enumerating objects: 99, done.
remote: Counting objects: 100% (99/99), done.
remote: Compressing objects: 100% (59/59), done.
remote: Total 1592 (delta 59), reused 68 (delta 40), pack-reused 1493
Receiving objects: 100% (1592/1592), 1.37 MiB | 2.08 MiB/s, done.
Resolving deltas: 100% (930/930), done.
poddingue@orangepizero:~$ cd termtosvg/
poddingue@orangepizero:~/termtosvg$ make build
rm -rf dist && \
python setup.py sdist bdist_wheel
usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help

error: invalid command 'bdist_wheel'
make: *** [Makefile:31: build] Error 1

Of course, the setuptools module can’t be found. I have not installed/configured Python2 setup tools. But as I had already done if for Python3, I modified the Makefile so that python is now python3, and pip is now pip3:

PIP=pip3
PYTHON=python3

If I had not done it yet, it would have looked that way:

poddingue@orangepizero:~/termtosvg$ sudo apt-get install python3-setuptools
Reading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
  python-setuptools-doc
The following NEW packages will be installed:
  python3-setuptools
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 306 kB of archives.
After this operation, 1,353 kB of additional disk space will be used.
Get:1 http://httpredir.debian.org/debian buster/main armhf python3-setuptools all 40.8.0-1 [306 kB]
Fetched 306 kB in 0s (1,225 kB/s)
Selecting previously unselected package python3-setuptools.
(Reading database ... 66711 files and directories currently installed.)
Preparing to unpack .../python3-setuptools_40.8.0-1_all.deb ...
Unpacking python3-setuptools (40.8.0-1) ...
Setting up python3-setuptools (40.8.0-1) ...

Now that Python setup tools are installed, let’s try it again:

poddingue@orangepizero:~/termtosvg$ make build
rm -rf dist && \
python3 setup.py sdist bdist_wheel
usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help

error: invalid command 'bdist_wheel'
make: *** [Makefile:31: build] Error 1

Ok, fair enough, I’ll install wheel thanks to pip.

pip3 install wheel
Collecting wheel
  Using cached https://files.pythonhosted.org/packages/00/83/b4a77d044e78ad1a45610eb88f745be2fd2c6d658f9798a15e384b7d57c9/wheel-0.33.6-py2.py3-none-any.whl
Installing collected packages: wheel
  The script wheel is installed in '/home/poddingue/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed wheel-0.33.6

And then, as gently suggested by pip3:

export PATH=/home/poddingue/.local/bin:$PATH

Ok, let’s try it again:

poddingue@orangepizero:~/termtosvg$ make build
rm -rf dist && \
python3 setup.py sdist bdist_wheel
running sdist
running egg_info
creating termtosvg.egg-info
writing termtosvg.egg-info/PKG-INFO
writing dependency_links to termtosvg.egg-info/dependency_links.txt
writing requirements to termtosvg.egg-info/requires.txt
writing top-level names to termtosvg.egg-info/top_level.txt
writing manifest file 'termtosvg.egg-info/SOURCES.txt'
reading manifest file 'termtosvg.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no previously-included files matching '__pycache__' found anywhere in distribution
warning: no previously-included files matching '*.pyc' found anywhere in distribution
warning: no files found matching 'man/*.man.?'
writing manifest file 'termtosvg.egg-info/SOURCES.txt'
running check
[...]
Creating tar archive
removing 'termtosvg-1.0.0' (and everything under it)
[...]
running build_scripts
[...]
installing to build/bdist.linux-armv7l/wheel
running install
running install_lib
[...]
Copying termtosvg.egg-info to build/bdist.linux-armv7l/wheel/termtosvg-1.0.0-py3.7.egg-info
[...]
adding 'termtosvg-1.0.0.dist-info/RECORD'
removing build/bdist.linux-armv7l/wheel

Happy? Not really…

poddingue@orangepizero:~/termtosvg$ pip3 install dist/termtosvg-1.0.0-py3-none-any.whl
Processing ./dist/termtosvg-1.0.0-py3-none-any.whl
Collecting pyte (from termtosvg==1.0.0)
  Downloading https://files.pythonhosted.org/packages/66/37/6fed89b484c8012a0343117f085c92df8447a18af4966d25599861cd5aa0/pyte-0.8.0.tar.gz (50kB)
    100% |████████████████████████████████| 51kB 768kB/s
Collecting lxml (from termtosvg==1.0.0)
  Downloading https://files.pythonhosted.org/packages/e4/19/8dfeef50623892577dc05245093e090bb2bab4c8aed5cad5b03208959563/lxml-4.4.2.tar.gz (2.6MB)
    100% |████████████████████████████████| 2.6MB 74kB/s
Collecting wcwidth (from termtosvg==1.0.0)
  Downloading https://files.pythonhosted.org/packages/58/b4/4850a0ccc6f567cc0ebe7060d20ffd4258b8210efadc259da62dc6ed9c65/wcwidth-0.1.8-py2.py3-none-any.whl
Building wheels for collected packages: pyte, lxml
  Running setup.py bdist_wheel for pyte ... done
  Stored in directory: /home/poddingue/.cache/pip/wheels/c0/dd/4a/d0ec26b9d07a3b48e25ba3456dc9bcab875686af6da9e23fcd
  Running setup.py bdist_wheel for lxml ... error
  Complete output from command /usr/bin/python3 -u -c "import setuptools, tokenize;__file__='/tmp/pip-install-4whala87/lxml/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" bdist_wheel -d /tmp/pip-wheel-w27wrikj --python-tag cp37:
  Building lxml version 4.4.2.
  Building without Cython.
  ERROR: b'/bin/sh: 1: xslt-config: not found\n'
  ** make sure the development packages of libxml2 and libxslt are installed **

  Using build configuration of libxslt
  running bdist_wheel
  running build
  running build_py
  [...]
  arm-linux-gnueabihf-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -DCYTHON_CLINE_IN_TRACEBACK=0 -Isrc -Isrc/lxml/includes -I/usr/include/python3.7m -c src/lxml/etree.c -o build/temp.linux-armv7l-3.7/src/lxml/etree.o -w
  In file included from src/lxml/etree.c:692:
  src/lxml/includes/etree_defs.h:14:10: fatal error: libxml/xmlversion.h: No such file or directory
   #include "libxml/xmlversion.h"
            ^~~~~~~~~~~~~~~~~~~~~
  compilation terminated.
  Compile failed: command 'arm-linux-gnueabihf-gcc' failed with exit status 1
  creating tmp
  cc -I/usr/include/libxml2 -c /tmp/xmlXPathInitp1a2wq_x.c -o tmp/xmlXPathInitp1a2wq_x.o
  /tmp/xmlXPathInitp1a2wq_x.c:1:10: fatal error: libxml/xpath.h: No such file or directory
   #include "libxml/xpath.h"
            ^~~~~~~~~~~~~~~~
  compilation terminated.
  *********************************************************************************
  Could not find function xmlCheckVersion in library libxml2. Is libxml2 installed?
  *********************************************************************************
  error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1
  [...]
Command "/usr/bin/python3 -u -c "import setuptools, tokenize;__file__='/tmp/pip-install-4whala87/lxml/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-record-5dypr5tk/install-record.txt --single-version-externally-managed --compile --user --prefix=" failed with error code 1 in /tmp/pip-install-4whala87/lxml/

Damn!

poddingue@orangepizero:~/termtosvg$ sudo apt install libxml2-dev libxslt-dev python3-dev
Reading package lists... Done
Building dependency tree
Reading state information... Done
Note, selecting 'libxslt1-dev' instead of 'libxslt-dev'
python3-dev is already the newest version (3.7.3-1).
The following additional packages will be installed:
  icu-devtools libicu-dev libxslt1.1
Suggested packages:
  icu-doc
The following NEW packages will be installed:
  icu-devtools libicu-dev libxml2-dev libxslt1-dev libxslt1.1
0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded.
Need to get 10.5 MB of archives.
After this operation, 44.6 MB of additional disk space will be used.
Do you want to continue? [Y/n]
Get:1 http://httpredir.debian.org/debian buster/main armhf icu-devtools armhf 63.1-6 [168 kB]
Get:2 http://httpredir.debian.org/debian buster/main armhf libicu-dev armhf 63.1-6 [8,934 kB]
Get:3 http://httpredir.debian.org/debian buster/main armhf libxml2-dev armhf 2.9.4+dfsg1-7+b3 [718 kB]
Get:4 http://httpredir.debian.org/debian buster/main armhf libxslt1.1 armhf 1.1.32-2.2~deb10u1 [217 kB]
Get:5 http://httpredir.debian.org/debian buster/main armhf libxslt1-dev armhf 1.1.32-2.2~deb10u1 [507 kB]
Fetched 10.5 MB in 5s (2,318 kB/s)
Selecting previously unselected package icu-devtools.
(Reading database ... 66802 files and directories currently installed.)
Preparing to unpack .../icu-devtools_63.1-6_armhf.deb ...
Unpacking icu-devtools (63.1-6) ...
Selecting previously unselected package libicu-dev:armhf.
Preparing to unpack .../libicu-dev_63.1-6_armhf.deb ...
Unpacking libicu-dev:armhf (63.1-6) ...
Selecting previously unselected package libxml2-dev:armhf.
Preparing to unpack .../libxml2-dev_2.9.4+dfsg1-7+b3_armhf.deb ...
Unpacking libxml2-dev:armhf (2.9.4+dfsg1-7+b3) ...
Selecting previously unselected package libxslt1.1:armhf.
Preparing to unpack .../libxslt1.1_1.1.32-2.2~deb10u1_armhf.deb ...
Unpacking libxslt1.1:armhf (1.1.32-2.2~deb10u1) ...
Selecting previously unselected package libxslt1-dev:armhf.
Preparing to unpack .../libxslt1-dev_1.1.32-2.2~deb10u1_armhf.deb ...
Unpacking libxslt1-dev:armhf (1.1.32-2.2~deb10u1) ...
Setting up icu-devtools (63.1-6) ...
Setting up libxslt1.1:armhf (1.1.32-2.2~deb10u1) ...
Setting up libicu-dev:armhf (63.1-6) ...
Setting up libxml2-dev:armhf (2.9.4+dfsg1-7+b3) ...
Setting up libxslt1-dev:armhf (1.1.32-2.2~deb10u1) ...
Processing triggers for man-db (2.8.5-2) ...
Processing triggers for libc-bin (2.28-10) ...

All set? Not so sure…

Command "/usr/bin/python3 -u -c "import setuptools, tokenize;__file__='/tmp/pip-install-9lgddbtt/lxml/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-record-ghyj96g8/install-record.txt --single-version-externally-managed --compile --user --prefix=" failed with error code 1 in /tmp/pip-install-9lgddbtt/lxml/

The error may have happened because of low memory. Let’s this machine breathe a little better by giving it more swap:

sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo nano /etc/fstab

Now, let’s add this line in /etc/fstab.

/swapfile swap swap defaults 0 0

Let’s try to install it once again. Go and have a coffee, or go for a walk… You could maybe even take time to marry and start a family before the build finishes. Anyway, after some time, you’ll have your beautiful termtosvg package ready to rock!

pip3 install dist/termtosvg-1.0.0-py3-none-any.whl
Processing ./dist/termtosvg-1.0.0-py3-none-any.whl
Collecting lxml (from termtosvg==1.0.0)
  Using cached https://files.pythonhosted.org/packages/e4/19/8dfeef50623892577dc05245093e090bb2bab4c8aed5cad5b03208959563/lxml
Requirement already satisfied: pyte in /home/poddingue/.local/lib/python3.7/site-packages (from termtosvg==1.0.0) (0.8.0)
Requirement already satisfied: wcwidth in /home/poddingue/.local/lib/python3.7/site-packages (from termtosvg==1.0.0) (0.1.8)
Building wheels for collected packages: lxml
  Running setup.py bdist_wheel for lxml ... -


done
  Stored in directory: /home/poddingue/.cache/pip/wheels/73/52/7c/5cd696851d3e5e31a05023dd402b04659a6ec695ecc566c9d3
Successfully built lxml
Installing collected packages: lxml, termtosvg
Successfully installed lxml-4.4.2 termtosvg-1.0.0

This time, it looks like our installation has been successful. Let’s try it for real now.

Testing

Just enter termtosvg, do your stuff, and then do exit. You will have a message that will tell you where your newly created file lies.

That’s cool, but can we do any better? The man page tells us we can record a terminal session with a specific screen geometry:

termtosvg -g 80x24 animation.svg
Recording started, enter "exit" command or Control-D to end
poddingue@orangepizero:~$ echo "Hello, World!"
Hello, World!
poddingue@orangepizero:~$ exit
Rendering ended, SVG animation is animation.svg

Pretty handy!

I am recording my casts with a 200x60 geometry, exporting them in the SVG format. The first step is to record them:

termtosvg record -g 200x60 whatever-name-I-choose.cast

I then ask termtosvg to render the cast as SVG, so that I can use them in websites that can handle them:

termtosvg render whatever-name-I-choose.cast whatever-name-I-choose.svg
Rendering started
Rendering ended, SVG animation is whatever-name-I-choose.svg

Want more? You’re GIF hungry? Ok I resign myself to give you my process, which is not pretty, and requires an X86 machine. As we have seen before, we can record a terminal session in asciicast v2 format. The cast file we have produced during the previous step to generate SVG, can then be used by asciinema (using phantomjs in the background) to produce a GIF file.

docker pull asciinema/asciicast2gif
alias asciicast2gif='docker run --rm -v $PWD:/data asciinema/asciicast2gif'
asciicast2gif -s 2 -t solarized-dark whatever-name-I-choose.cast demo.gif

And here is the final result:

whatever-name-I-choose

And in GIF for the CMS that can’t display properly SVG.

whatever-name-I-choose.cast