Запись и рендеринг терминала
Наверняка, вы видели в репозиториях GitHub README с анимацией консоли. Честно говоря до недавнего времени их назначение, кроме как “вау, красиво” было мне непонятно, но достаточно сложные операции и специфичная целевая аудитория заставила обратиться и к вот такому способу донесения информации.
Запись и рендеринг терминала
Запись терминала на видео конечно сделать можно, но это страшно неудобно. Во-первых, результат никак не отредактируешь, надо все перезаписывать. Во-вторых, качество сильно страдает.
Какие вообще варианты
К счастью, прогресс не стоит на месте, и добрые люди напилили вариантов решения проблемы.
asciinema
asciinema - самая популярная утилита для записи терминала.
Записывает команды и вывод в файл .cast, который можно редактировать. Достаточно умна прямо из коробки, чтобы не записывать паузы и ожидания. Результат можно редактировать!
Не работает на Windows, работает из коробки со всякими виртуальными средами, что очень удобно.
Есть готовый плеер asciinema-player для проигрывания .cast в веб-документации, так что даже рендерить отдельно ничего не нужно. Также может записывать результат выполнения .sh
скрипта (подробнее с примером тут).
VHS
vhs - исполняет команды из файла и записывает в gif (опционально в .cast).
Работает на Windows, можно юзать с PowerShell с некоторыми ограничениями. Легко установить и настроить, хорошая документация.
Эта очень симпатичная и простая утилита, к тому же её можно использовать просто для выполнения команд из файла, как scriptplay. Работает быстро, но редактировать результат нельзя, поэтому ищем дальше.
termtosvg
termtosvg - старенький, но удаленький. Записывает терминал в анимацию svg. Прост как тапок и симпатичен. Не работает на Windows, но зато умеет конвертировать .cast записи в svg, что очень удобно.
Есть несколько готовых шаблонов. Результирующий SVG содержит все необходимое для анимации: JavaScript, CSS, опционально кнопки плеера. Гигантский плюс SVG также в том, что текст из анимированного окошка терминала можно копировать.
terminalizer
terminalizer еще одна популярная модная утилита на NodeJS. Наверное он хорош, но у меня что на Windows, что на Ubuntu 22.04 сыпались ошибки. Плюс на Windows надо ставить длинную кочергу из Build C++ Tools, просто чтобы это собрать.
Короче, terminalizer был в списке на тестирование последним, ему не повезло, и после третьей ошибки он отправился на обочину этой истории.
Что в результате выбрали
Реализовали два варианта:
- Запись gif: asciinema > agg > gifsicle. Получаем оптимизированную gif-ку, которую легко пересобрать из cast и почти без проблем вставить куда угодно.
- Запись svg: asciinema > termtosvg. Получаем готовый объект .svg с плеером, который прям совсем без проблем можно вставить куда угодно, в том числе локальные HTML.
asciinema по результатам тестов показала себя лучше остальных рекордеров. Никаких ошибок в работе, есть запись таймингов, готовый касты можно ускорять или замедлять. Богатая документация и активное сообщество. Короче говоря, дальше можно не искать.
До конца не решена проблема с записью PowerShell, так как ни одна из утилит (включая terminalizer и vhs, которые хотя бы пытаются), не работает без костылей.
Делаем красивый GIF с agg и gifsicle
В этом варианте asciinema файл .cast
конвертируется в gif утилитой agg, которая доступна и для ОС Windows.
Gif получится большой и красивый, такой большой, что нужно его оптимизировать утилитой gifsicle. На Linux утилита ставится просто, а для Windows нужно скачать бинарник тут.
Workflow:
- Начинаем запись команд и результата:
asciinema rec filename.cast
. - Выполняем команды, как обычно.
- Для остановки записи нажимаем
Ctrl + D
. - Обрабатываем каст:
.\agg.exe --font-size 20 --speed 2 --rows 16 --fps-cap 10 .\filename.cast filename_idle.gif
(speed
- ускорят каст,rows
- изменяет высоту консоли,fps-cap
- ограничивает FPS и размер файла). - Оптимизируем gif:
.\gifsicle.exe --lossy=220 -k 16 -O3 -Okeep-empty .\filename.gif -o filename_opt.gif
(гайд по командам).
Gif в веб-документации
Если у вас веб-документация с веб-сервером, то у вас все в ажуре. Вы можете использовать для проигрывания терминалов asciinema-player вообще без gif или любой из доступных gif плееров.
Если же вы, как и я, ограничены протоколом file://
(то есть юзер открывает html с диска, никакого веб-сервера нету), то удобное воспроизведение gif становится проблемой. asciinema-player не заработает из-за cross origin error и это также относится к любому цивильному JavaScript плееру. Что же делать?
Я использую для генерации HTML из markdown один из шаблонов pandoc (easy-templates).
Один из немногих плееров, который заработал с протоколом file://
: gifa11y. Автономный мини-плеер, который можно скачать и добавить в свой шаблон.
Вот так я добавила плеер в шаблон easy_template.
В конце шаблона после последнего <script>
добавляем (assets/js/gifa11y.js
– путь к js файлу библиотеки на диске):
<script type='text/javascript' src="assets/js/gifa11y.js"></script>
<script type="text/javascript">
var gifa11y = new Gifa11y({
container: 'body',
buttonBackground: '#000000',
buttonBackgroundHover: '#404040',
buttonIconColor: 'white',
initiallyPaused: true
});
</script>
По умолчанию gif не запускаются, нужно нажать play для проигрывания. К сожалению, они проигрываются один раз, для повторного воспроизведения надо перезагрузить страницу. Зато никаких cross-origin ошибок!
Записываем консоль в SVG с плеером
После записи и обработки 123-много кастов терминала, именно вариант с SVG я использую чаще всего. Одна из причин - удобный плеер, который работает на локальных HTML-файлах (т.е. файлы, которые сервятся по протоколу file://
, а не с веб-сервера по http://
).
В этом варианте .cast, записанный asciinema’ой обрабатывается termtosvg с кастомным шаблоном для получения готовой анимации с плеером!
Вот так выглядит терминал SVG со встроенным плеером. Симпатично, да? А главное практически никаких лишних телодвижений!
Вот как это делается:
- Начинаем запись команд и результата:
asciinema rec filename.cast
. - Выполняем команды, как обычно.
- Для остановки записи нажимаем
Ctrl + D
. - Получившийся .cast конвертируем в .svg. Методом проб и ошибок я подобрала оптимальную команду:
termtosvg render filename.cast filename.svg -t window_frame_js -m 17 -M 2000 -D 5000
.
Размер окна терминала можно отредактировать в .cast файле в первой строке перед конвертацией в .svg. Я обычно ставлю width
где-то 80-90, height
- 20-35.
Готовый svg можно прям сразу проиграть, открыв в бразуере. А встроить в HTML нужно с помощью тега object
, вот так:
<div>
<p align="center">
<object data="filename.svg"></object>
</p>
<p align="center">
Красивая консолька с анимацией
</p>
</div>
Если генерируем из markdown, то прямо так с div и вставляем, pandoc достаточно умный, чтоб обработать блок, как нужно.
Почти все! Идеал близко. К сожалению, шаблон window_frame_js
всем красив, но анимация начинается сразу при загрузке страницы. И если на странице таких анимаций много, то все они запустятся одновременно. Нехорошо.
Поэтому делаем следующее:
- Загружаем из репозитория termtosvg наш шаблон
window_frame_js.svg
. - Ищем в шаблоне строку
var is_playing = true;
. - После нее вставляем вот такой блок:
if (is_playing) {
animation.pause()
play_button.setAttribute('display', 'inline')
pause_button.setAttribute('display', 'none')
is_playing = false
}
Вот теперь идеально. Анимация будет запускаться только по нажатию кнопки play. Чтобы использовать наш шаблон, укажите путь к нему в флаге -t
: termtosvg render filename.cast filename.svg -t template/window_frame_js.svg -m 17 -M 2000 -D 5000
.
Бонус! Прячем анимацию в условные блоки
Так как у меня документация генерируется в docx, pdf и html, то всю эту красивую анимацию надо бы как-то выборочно вставлять, так чтобы в docx и pdf их не было.
Для этого нужно реализовать условные блоки. Как обычно, добрые люди уже все разработали, осталось только взять и прикрутить куда-надо.
Встречайте: панда! panda - коллекция lua-фильтров, в числе которых обработка переменных, условные блоки, генерация диаграмм, исполнение кода и включение из файлов.
Практически все работает именно как написано, кроме разве что включения файлов (неполноценная реализация, которая плохо обрабатывает относительные пути).
Применяется как обычный lua фильтр к pandoc, с флагом --lua-filter panda.lua
.
В panda много всего интересного, но сейчас нас интересуют только условные блоки. Вот как я это сделала:
- В преамбуле документа есть переменная
html: true
. -
Вся анимация в .md лежит вот в таких блоках. Блок будет рендериться в документе, только если переменная
html
равнаtrue
.:::{.if html="true"} <div> <p align="center"> <object data="filename.svg"></object> </p> </div> :::
- Если нужно собрать docx или pdf, то просто передаем pandoc новое значение html в команде:
-M html=false
. Все, эти блоки в документе отображаться не будут. Не знаю, как я раньше без этих блоков жила?!