如何在Hugo博客中使用终端播放器Asciinema-Player

目录

Asciinema-Player是一款著名的的终端录制播放器,可用于播放asciinema录制的播放文件,经常被用来进行终端操作演示。这里也将其引入到了当前Hugo博客中使用,下面我将讲讲引入的过程。

Talk is cheap, show the result ~

这里直接借用官方demo进行展示。

> # Welcome to asciinema!                                                                                               > # See how easy it is to record a terminal session                                                                     > # First install the asciinema recorder                                                                                > brew install asciinema                                                                                                ==> Downloading https://homebrew.bintray.com/bottles/asciinema-2.0.2_2.catalina.bottle.1.tar.gz                         ==> Downloading from https://akamai.bintray.com/4a/4ac59de631594cea60621b45d85214e39a90a0ba8ddf4eeec5cba34bd6145711     ######################################################################## 100.0%                                         ==> Pouring asciinema-2.0.2_2.catalina.bottle.1.tar.gz                                                                  🍺  /usr/local/Cellar/asciinema/2.0.2_2: 613 files, 6.4MB                                                               > # Now start recording                                                                                                 > asciinema rec                                                                                                         asciinema: recording asciicast to /tmp/u52erylk-ascii.cast                                                              asciinema: press <ctrl-d> or type "exit" when you're done                                                               bash-3.2$ # I am in a new shell instance which is being recorded now                                                    bash-3.2$ sha1sum /etc/f* | tail -n 10 | lolcat -F 0.3                                                                  da39a3ee5e6b4b0d3255bfef95601890afd80709  /etc/find.codes                                                               88dd3ea7ffcbb910fbd1d921811817d935310b34  /etc/fstab.hd                                                                 442a5bc4174a8f4d6ef8d5ae5da9251ebb6ab455  /etc/ftpd.conf                                                                442a5bc4174a8f4d6ef8d5ae5da9251ebb6ab455  /etc/ftpd.conf.default                                                        d3e5fb0c582645e60f8a13802be0c909a3f9e4d7  /etc/ftpusers                                                                 bash-3.2$ # To finish recording just exit the shell                                                                     bash-3.2$ exit                                                                                                          exit                                                                                                                    asciinema: recording finished                                                                                           asciinema: press <enter> to upload to asciinema.org, <ctrl-c> to save locally                                                                                                                                                                   https://asciinema.org/a/17648                                                                                           > # Open the above URL to view the recording                                                                            > # Now install asciinema and start recording your own sessions                                                         > # Oh, and you can copy-paste from here                                                                                > # Bye!                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
00:00-01:59
"Hello asciinema!"

为什么需要在Hugo里面使用终端播放器Asciinema-Player呢?

作为技术人员,在写博客时总是需要进行一些终端操作演示,而演示方式无非以下几种:

方式 优点 缺点
视频 能配音配特效 网站流量消耗大,不能复制文本
动图 文件相对小些 不能控制进度,不能复制文本
代码 占用流量小,能复制 不能控制进度,也不太好展示效果

而终端录制播放器Asciinema-Player则兼容体积小、能控制、能复制于一体并能完美复现终端操作场景,非常适合用于在博客中进行一些终端操作演示。

首先,在网上找下前人在Hugo博客里扩展嵌入asciinema-player的方法,比如 Embedding asciinema cast in your Hugo site

可以找到里面asciinema-player的shortcode代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<p>
    <asciinema-player
        src="/casts/{{ with .Get "key" }}{{ . }}{{ end }}.cast"
        cols="{{ if .Get "cols" }}{{ .Get "cols" }}{{ else }}640{{ end }}"
        rows="{{ if .Get "rows" }}{{ .Get "rows" }}{{ else }}10{{ end }}"
        {{ if .Get "autoplay" }}autoplay="{{ .Get "autoplay" }}"{{ end }}
        {{ if .Get "preload" }}preload="{{ .Get "preload" }}"{{ end }}
        {{ if .Get "loop" }}loop="{{ .Get "loop" }}"{{ end }}
        start-at="{{ if .Get "start-at" }}{{ .Get "start-at" }}{{ else }}0{{ end }}"
        speed="{{ if .Get "speed" }}{{ .Get "speed" }}{{ else }}1{{ end }}"
        {{ if .Get "idle-time-limit" }}idle-time-limit="{{ .Get "idle-time-limit" }}"{{ end }}
        {{ if .Get "poster" }}poster="{{ .Get "poster" }}"{{ end }}
        {{ if .Get "font-size" }}font-size="{{ .Get "font-size" }}"{{ end }}
        {{ if .Get "theme" }}theme="{{ .Get "theme" }}"{{ end }}
        {{ if .Get "title" }}title="{{ .Get "title" }}"{{ end }}
        {{ if .Get "author" }}author="{{ .Get "author" }}"{{ end }}
        {{ if .Get "author-url" }}author-url="{{ .Get "author-url" }}"{{ end }}
        {{ if .Get "author-img-url" }}author-img-url="{{ .Get "author-img-url" }}"{{ end }}
    ></asciinema-player>
</p>

细细口味了下这段代码,结合自己需求进行了修改:

Talk is cheap, show me the code ~
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# themes/iLoveIt/layouts/shortcodes/asciinema.html
<p>
    <asciinema-player
        {{- with .Get "src" }} src="{{ . }}" {{ end -}}
        {{- with .Get "key" }} src="/casts/{{ . }}.cast"{{ end -}}
        cols="{{ if .Get "cols" }}{{ .Get "cols" }}{{ else }}640{{ end }}"
        rows="{{ if .Get "rows" }}{{ .Get "rows" }}{{ else }}10{{ end }}"
        {{- with .Get "autoplay" }}autoplay="{{ . }}"{{ end -}}
        {{- with .Get "preload" }}preload="{{ . }}"{{ end -}}
        {{- with .Get "loop" }}loop="{{ . }}"{{ end -}}
        start-at="{{ if .Get "start-at" }}{{ .Get "start-at" }}{{ else }}0{{ end }}"
        speed="{{ if .Get "speed" }}{{ .Get "speed" }}{{ else }}1{{ end }}"
        {{- with .Get "idle-time-limit" }}idle-time-limit="{{ . }}"{{ end -}}
        {{- with .Get "poster" }} poster="{{ . | safeURL }}"{{ end -}}
        {{- with .Get "font-size" }}font-size="{{ . }}"{{ end -}}
        {{- with .Get "theme" }}theme="{{ . }}"{{ end -}}
        {{- with .Get "title" }}title="{{ . }}"{{ end -}}
        {{- with .Get "author" }}author="{{ . }}"{{ end -}}
        {{- with .Get "author-url" }}author-url="{{ . }}"{{ end }}
        {{- with .Get "author-img-url" }}author-img-url="{{ . }}"{{ end -}}
        fit="{{ if .Get "fit" }}{{ .Get "fit" }}{{ else }}width{{ end }}"
    ></asciinema-player>
    {{- .Page.Scratch.SetInMap "this" "asciinema" true -}}
</p>

注意看高亮部分,主要修改了以下几点:

修改点
  1. if .Get 形式代码过于累赘,这里把不需要取默认值的语句统统改成了with .Get形式;
  2. 只有固定的key方法从本站获取.cast录制文件,这里扩展了src以便从站外获取录制文件地址;
  3. poster这里会得到一个奇怪的数据#ZgotmplZ,它是一个安全防护的默认数据,见官方说明,会导致设置指定时间封面无效,解决起来也简单,加上| safeURL管道方法就可解决;

参考方案里动态引入jscss,用到的该播放器的文章里需设置asciinematrue

1
2
3
{{ if .Params.asciinema }}
    <script src="{{ .Site.BaseURL }}js/asciinema-player.js"></script>
{{ end }}
1
2
3
4
5
6
7
8
---
title: Kubernetes Backup - ARK
description: Kubernetes backup process using ark
asciinema: true
tags:
  - kubernetes
  - backup
---

可以看到上面是通过在文章里加asciinema参数实现的jscss资源动态加载,其实可以参考其它如mermaid的接入方式,直接在渲染时置标记就好:

122
123
124
125
126
127
128
129
# themes/iLoveIt/layouts/partials/assets.html
{{- /* asciinema */ -}}
{{- if (.Scratch.Get "this").asciinema | or $params.draft -}}
    {{- $source := "lib/asciinema/asciinema-player.min.css" -}}
    {{- dict "Source" $source "Fingerprint" $fingerprint | dict "Scratch" .Scratch "Data" | partial "scratch/style.html" -}}
    {{- $source := "lib/asciinema/asciinema-player.min.js" -}}
    {{- dict "Source" $source "Fingerprint" $fingerprint | dict "Scratch" .Scratch "Data" | partial "scratch/script.html" -}}
{{- end -}}

其中$params.draft是为了开发模式下也能设置草稿参数进行预览:

53
54
55
56
57
58
59
60
# themes/iLoveIt/assets/data/cdn/jsdelivr.yml
  gitalkJS: gitalk@1.7.2/dist/gitalk.min.js
  # valine@1.5.0 https://valine.js.org/
  valineJS: valine@1.5.0/dist/Valine.min.js
  asciinemaJS: asciinema-player@3.0.1/dist/index.min.js
  # cookieconsent@3.1.1 https://github.com/osano/cookieconsent
  cookieconsentCSS: cookieconsent@3.1.1/build/cookieconsent.min.css
  cookieconsentJS: cookieconsent@3.1.1/build/cookieconsent.min.js

调整下css样式,让播放器能够在超出显示区域时滚动过去:

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/* themes/iLoveIt/assets/lib/asciinema/asciinema-player.min.css */
.asciinema-terminal {
  box-sizing: content-box;
  -moz-box-sizing: content-box;
  -webkit-box-sizing: content-box;
  padding: 0px;
  margin: 0px;
  display: block;
  white-space: pre;
  word-wrap: normal;
  word-break: normal;
  border-radius: 5px;
  border-style: solid;
  cursor: pointer;
  border-width: 0.3em;
  font-family: Consolas, Menlo, 'Bitstream Vera Sans Mono', monospace, 'Powerline Symbols', 'Terminal Glyphs';
  line-height: 1.3333333333em;
  width: auto;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
}
.asciinema-terminal .line {
  letter-spacing: normal;
  overflow: hidden;
  height: 1.3333333333em;
}
.asciinema-terminal .line span {
  padding: 0;
  display: inline-block;
  height: 1.3333333333em;
}
.asciinema-terminal .line {
  display: block;
  width: 4000px;
}

可以注意到font-family额外加了Terminal Glyphs,这也可参考了 Custom Fonts in Asciinema。 主要是为了不至于出现下文中乱码的问题。正常如下:

                                                                                                                                                                                                                                                                                                                                                                                                                ╭─  …/Code/blog   master                                                                                                                                                                       2.38G   91% (6:00)   22:18:33                                                                                                                                                                    ╰ exi                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
00:00-00:03

asciinema-player即播放器,在博客中使用只需简单在文章中加入以下代码:

1

   476     doc.set_selection(view.id, Selection::point(pos));                                                         477 }                                                                                                              4 8                                                                                                                4 9 fn go┌──────────────────────────────────────────────┐┌──────────────────────────────────────────────┐          480     g                                             ││ use serde::de::{self, Deserialize, Deseriali   481 } ──────────────────────────────────────────────││     482  Context                                    ││ pub struct Context<'a> {    483 fn goselected_register                          ││     pub selected_register: helix_view::Regis   484     gcount                                      ││     pub count: Option<NonZeroUsize>,    485 } editor                                     ││     pub editor: &'a mut Editor,    486  callback                                   ││     487 fn goon_next_key_callback                       ││     pub callback: Option<crate::compositor::   488     gjobs                                       ││     pub on_next_key_callback: Option<Box<dyn   489 } impl Context<'a>                           ││     pub jobs: &'a mut Jobs,    490  > push_layer                                 ││ }    491 fn moon_next_key                                ││     492     lcallback                                   ││ impl<'a> Context<'a> {    493     lcount                                      ││     /// Push a new component onto the compos   494     lAlign                                      ││     pub fn push_layer(&mut self, component:    495  Top                                        ││         self.callback = Some(Box::new(|compo     6     lCenter                                     ││             compositor.push(component)      7      Bottom                                     ││         }));      8      align_view                                 ││     }      9      Command                                    ││       0     dname                                       ││     #[inline]      1 } fun                                        ││     pub fn on_next_key(      2  doc                                        ││         &mut self,      3 fn mocommands                                   ││         on_next_key_callback: impl FnOnce(&m     4     limpl Command                               ││     ) {      5     lexecute                                    ││         self.on_next_key_callback = Some(Box     6     lname                                       ││     }      7      └──────────────────────────────────────────────┘└──────────────────────────────────────────────┘            8     let selection = doc                                                                                             helix-term/src/commands.rs[+]                                                                       0    496:16                                                                                                               
00:00-01:37

其中,colsrows分别为行列,preload则是要不要预加载,poster则是封面,有data模式也有npt模式,npt即按时间截取封面,上面的就是截取55秒时的终端作为封面。 另外还有speed:播放速度,autoplay:自动播放等参数,具体可参考asciinema-player设置

呈现效果如下(借用了helix-editor的演示,文件大些可能加载慢些):

   476     doc.set_selection(view.id, Selection::point(pos));                                                              477 }                                                                                                                   478                                                                                                                     479 fn go┌──────────────────────────────────────────────┐┌──────────────────────────────────────────────┐               480     g                                             ││ use serde::de::{self, Deserialize, Deseriali   481 } ──────────────────────────────────────────────││     482  Context                                    ││ pub struct Context<'a> {    483 fn goselected_register                          ││     pub selected_register: helix_view::Regis   484     gcount                                      ││     pub count: Option<NonZeroUsize>,    485 } editor                                     ││     pub editor: &'a mut Editor,    486  callback                                   ││     487 fn goon_next_key_callback                       ││     pub callback: Option<crate::compositor::   488     gjobs                                       ││     pub on_next_key_callback: Option<Box<dyn   489 } impl Context<'a>                           ││     pub jobs: &'a mut Jobs,    490  > push_layer                                 ││ }    491 fn moon_next_key                                ││     492     lcallback                                   ││ impl<'a> Context<'a> {    493     lcount                                      ││     /// Push a new component onto the compos   494     lAlign                                      ││     pub fn push_layer(&mut self, component:    495  Top                                        ││         self.callback = Some(Box::new(|compo   496     lCenter                                     ││             compositor.push(component)    497      Bottom                                     ││         }));    498      align_view                                 ││     }    499      Command                                    ││     500     dname                                       ││     #[inline]    501 } fun                                        ││     pub fn on_next_key(    502  doc                                        ││         &mut self,    503 fn mocommands                                   ││         on_next_key_callback: impl FnOnce(&m   504     limpl Command                               ││     ) {    505     lexecute                                    ││         self.on_next_key_callback = Some(Box   506     lname                                       ││     }    507      └──────────────────────────────────────────────┘└──────────────────────────────────────────────┘               508     let selection = doc                                                                                           NOR    helix-term/src/commands.rs[+]                                                                       0    496:16                                                                                                                         
00:00-01:37

点击播放按钮播放,可拖动进度条或者使用方向键控制播放进度。

上述终端播放所使用的cast文件,都是使用asciinema在终端录制的。

1
brew install asciinema
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
➜  ~ asciinema --help
usage: asciinema [-h] [--version] {rec,play,cat,upload,auth} ...

Record and share your terminal sessions, the right way.

positional arguments:
  {rec,play,cat,upload,auth}
    rec                 Record terminal session
    play                Replay terminal session
    cat                 Print full output of terminal session
    upload              Upload locally saved terminal session to asciinema.org
    auth                Manage recordings on asciinema.org account

options:
  -h, --help            show this help message and exit
  --version             show program's version number and exit

example usage:
  Record terminal and upload it to asciinema.org:
    asciinema rec
  Record terminal to local file:
    asciinema rec demo.cast
  Record terminal and upload it to asciinema.org, specifying title:
    asciinema rec -t "My git tutorial"
  Record terminal to local file, limiting idle time to max 2.5 sec:
    asciinema rec -i 2.5 demo.cast
  Replay terminal recording from local file:
    asciinema play demo.cast
  Replay terminal recording hosted on asciinema.org:
    asciinema play https://asciinema.org/a/difqlgx86ym6emrmd8u62yqu8
  Print full output of recorded session:
    asciinema cat demo.cast

For help on a specific command run:
  asciinema <command> -h

另外如果想将其转化为gif动图,也可以使用asciicast2gif

通过以上实践,虽然过程有些曲折,但最终还是顺利地将asciinema-player终端播放器引入到了hugo中使用,后续发布的博客也将经常见到其身影。

来发评论吧~
Powered By Valine
v1.5.0