Запись и рендеринг терминала

8 мин на чтение

Наверняка, вы видели в репозиториях 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:

  1. Начинаем запись команд и результата: asciinema rec filename.cast.
  2. Выполняем команды, как обычно.
  3. Для остановки записи нажимаем Ctrl + D.
  4. Обрабатываем каст: .\agg.exe --font-size 20 --speed 2 --rows 16 --fps-cap 10 .\filename.cast filename_idle.gif (speed - ускорят каст, rows - изменяет высоту консоли, fps-cap - ограничивает FPS и размер файла).
  5. Оптимизируем 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 со встроенным плеером. Симпатично, да? А главное практически никаких лишних телодвижений!

Вот как это делается:

  1. Начинаем запись команд и результата: asciinema rec filename.cast.
  2. Выполняем команды, как обычно.
  3. Для остановки записи нажимаем Ctrl + D.
  4. Получившийся .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 всем красив, но анимация начинается сразу при загрузке страницы. И если на странице таких анимаций много, то все они запустятся одновременно. Нехорошо.

Поэтому делаем следующее:

  1. Загружаем из репозитория termtosvg наш шаблон window_frame_js.svg.
  2. Ищем в шаблоне строку var is_playing = true;.
  3. После нее вставляем вот такой блок:
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 много всего интересного, но сейчас нас интересуют только условные блоки. Вот как я это сделала:

  1. В преамбуле документа есть переменная html: true.
  2. Вся анимация в .md лежит вот в таких блоках. Блок будет рендериться в документе, только если переменная html равна true.

       
    
    :::{.if html="true"}
    <div>
      <p align="center">
        <object data="filename.svg"></object>
      </p>
    </div>
    :::
       
    
  3. Если нужно собрать docx или pdf, то просто передаем pandoc новое значение html в команде: -M html=false. Все, эти блоки в документе отображаться не будут. Не знаю, как я раньше без этих блоков жила?!

Метки:

Разделы:

Дата изменения: