自动化终端录制并转成gif

问题

在我兴致勃勃写了一个 cli 工具(也就是 TyStrings )的时候, 希望在 README 中添加一个 gif 来展示它酷炫的效果.

通常, 我们会这么做:

这相当麻烦. 人为的演示很容易出错, 总要反反复复录制, 后期编辑也非常麻烦.

如果后续维护时, 添加了新的命令, 抑或是仅仅修改了输出, 那么就又要录一次.

这显然很槽糕.

自动化CLI演示的解决方案

我列出了我的自动化需求:

1. 通过脚本执行, 从 录制 到 导出 gif.

2. 脚本演示CLI, 无需人工干预.

我寻找了很久, 可以并没有找到对口的解决方案.

摸索了很久, 最后结合 ttygif + AppleScript + iTerm 2 来做这件事.

ttygif

ttyrec 是录制 tty 的命令行工具

sugyan/ttygif 基于 ttyrec, 但它将 ttyrec 的输出转换成 gif.

所以, 我们使用 ttygif 就好了

AppleScript

Introduction to AppleScript Language Guide

苹果开发的一个脚本语言. 客观来说, 比较偏门. 但是并非找不到它的身影.

因为我想, 你在 macOS 下见过它:

Automator.app

Automator 中, 你可以通过 AppleScript 来编写自动化脚本.

Automator 就是专门为流程自动化而生. 在 Automator 中可以方便的操作各种应用.

在macOS下, 开发者可以使用 XCode 为 macOS 软件添加Automator Action, 从而为应用程序提供 Automator 的支持.

但当我开始使用 Automator 之后… 发现… 我… 不太会拖拖控件…

所以, 最后选择手写 AppleScript.

iTerm 2

iTerm 2 是 macOS 下的一款终端软件.

之所以选择它是因为, 其添加了对 Automator(AppleScript) 的支持. 我们可以通过它提供的 AppleScript API 来操作终端.

iTerm 2 的 AppleScript 文档在: iTerm 2 documentation-scriptin

CLIDemo.scpt

示例源码

我编写了一些方法, 并写了一个 Demo 放到了 gist 上.

你可以下载下来, 通过 Script Editor.app 对它进行编辑和运行.

Script Editor.app

使用方法

- typingStringWithDelay(content, dy)

模拟打字, 在 iTerm 中输入内容, content为待输入的内容, dy 为每个字的输入间隔时间

- typingString(content)

间隔 0.1秒的模拟打字

- runCommand(command)

执行某个命令

- clear()

清空 iTerm, 即执行 clear 命令

- isAppRunning(appName)

检查某个App是否在运行

- setWindowTitle(title)

设置 iTerm 窗口 title

- activateiTerms()

创建/激活 iTerm 窗口

- getCurrentPath()

获取当前脚本所在路径

只要将该部分代码替换成自己的就行.

# — — — — Replace Me — — — -
# …替换部分代码
# — — — — Replace Me End — — — -

最终效果

录制完之后, 最后的结果如下图所示:

tty-gif-automation demo

在 TyStrings 上的应用

上面提到, 这个需求实际上源于我开发 TyStrings 的时候.

这是 TyStrings 对应的生成演示图的 AppleScript:

这里我只摘了 Replace Me 对应的代码, 剩余的代码与上面重复, 就不再贴出来了.
完整的可以查看 https://github.com/luckytianyiyan/TyStrings/blob/master/script/tystrings.scpt
set currentPath to getCurrentPath() as alias
activateiTerms()
setWindowTitle(“TyStrings Demo”)
# cd to repo root path
runCommand(“cd “ & (quoted form of POSIX path of currentPath) & “../”)
runCommand(“source venv/bin/activate”)
runCommand(“cd tests/example/”)
clear()
runCommand(“ttyrec ../../resource/recording”)
delay 0.5
runTyStringsSubCommand(“generate”, {“$(find . -name \\*.m)”, “-o”, “en.lproj zh-Hans.lproj”})
delay 2
clear()
runTyStringsSubCommand(“translate”, {“strings/base_translator.strings”, “zh-Hans.lproj/base_translator.strings”, “ — dst-lang”, “zh”, “ — src-lang”, “en”})
delay 2
clear()
runTyStringsSubCommand(“diff”, {“strings/diff1.strings”, “strings/diff2.strings”})
delay 2
clear()
runTyStringsSubCommand(“lint”, {“strings/lint.strings”})
delay 2
runCommand(“exit”)
delay 0.5
runCommand(“cd “ & (quoted form of POSIX path of currentPath) & “../”)
runCommand(“deactivate”)
runCommand(“ttygif resource/recording -f”)
runCommand(“mv tty.gif resource/tystrings.gif”)
# clean
runCommand(“rm -rf tests/example/en.lproj”)
runCommand(“rm -rf tests/example/zh-Hans.lproj”)
# — — — — TyStrings Helper — — — -
on runTyStringsSubCommand(subcommand, args)
typingString(“tys”)
typingStringWithDelay(“trings “, 0)
typingString(subcommand & “ “)
repeat with i from 1 to count of args
typingStringWithDelay(item i of args & “ “, 0.02)
end repeat
# typing ‘\n’
typingString(“
“)
end runTyStringsSubCommand

最终 TyStrings 的展示结果

https://raw.githubusercontent.com/luckytianyiyan/TyStrings/master/resource/tystrings.gif