asopi tech
asopi techIndie Developer
AstroにVHSを埋め込むには?

【2026年8月版】

AstroにVHSを埋め込むには?

公開日: 2026/08/17
読了時間: 約 13分

ターミナルの操作をブログで説明するとき、コマンドをコードブロックに貼るだけでは、どこに入力して、どんな結果が出たのかが分かりにくいです。

そこで、あそぴテックでは、コマンドの入力と結果を見せられるVHSを導入してみました。VHSで作ったデモをAstroのMarkdown記事に埋め込めるようにしました。この記事では、その取り組みを紹介します。

VHSとは

VHSは、.tapeファイルに書いた台本のコマンドを仮想ターミナル上で実際に実行し、その画面を録画してGIF、MP4、WebM、PNGを作るオープンソースのターミナルツールです。入力速度、待ち時間、画面サイズも台本で指定できます。

画面に映るのは実際のコマンドの出力です。手で操作する画面録画と違い、何をどの速さで打ち、どこで何秒待つかを台本に書いておけるため、録画のたびに同じ入力を再現できます。打ち間違いや操作の速さのばらつきが映り込みません。ブログ用のMP4、X用のGIF、結果を示すPNGも同じ台本から作れます。

この記事に掲載しているデモも、同じ方法で生成しました。

VHSの台本から生成したブログ埋め込みデモ

動画を開く

VHSのOutput.txtまたは.asciiを指定すると、最終的なターミナルの内容をテキストファイルへ出力できます。

VHSの生成とAstroの表示を分ける

このブログは静的サイトです。Astroのビルドで全ページをHTMLとして書き出し、配信するのはそのファイルだけです。読者のリクエストに応じて動くサーバー処理はありません。

VHSは.tapeに書いたコマンドを実行し、ttyd、Chromium、ffmpegを使って動画ファイルを書き出すコマンドラインツールです。動かす場所はビルドの中しかありません。

そこで、VHSでメディアを生成する処理と、Astroで記事に表示する処理を二段階に分けました。

  1. VHSの台本を仮想ターミナルで実行し、動画、GIF、PNG、テキストを生成します。
  2. AstroのMarkdown処理で、生成済みのメディアをHTMLへ変換して表示します。

VHSの台本から生成物を作る

ここではサンプルとして、echoを使った台本から、コマンドの入力、処理中、結果の流れを見せるターミナルデモを作成します。

Output demo.mp4
Output demo.gif
Output demo.txt

Require echo
Set Shell "bash"
Set Width 960
Set Height 540
Set FontSize 22
Set Padding 8
Set CursorBlink false
Set Framerate 10
Set TypingSpeed 65ms

Hide
Type "clear"
Enter
Type "echo 'analyzing sample-project...'"
Enter
Sleep 1s
Type "echo 'files: 42  dependencies: 8  status: ready'"
Enter
Sleep 2s
Screenshot demo.png
Show

この台本では、Set WidthSet Heightでターミナルのサイズを指定し、TypeEnterSleepで入力と処理中の時間を表現しました。

このブログでは、MP4、GIF、PNGをこのサイズで生成し、表示時は縦横比を保つようにしました。

録画し直すかどうかは、生成スクリプト側で判断します。リポジトリ直下の .vhs-render-manifest.jsonに、台本・Source・録画用Dockerfileと生成物の SHA-256を保存し、内容が変わった台本だけを録画し直します。Git clone後に ファイルの更新時刻が変わっても、内容が同じならビルドのたびに録画し直さずに 済みます。

Outputで動画、GIF、テキストを、Screenshotでポスター画像を生成する構成にしました。

AstroのMarkdownでメディアを表示する

記事へ埋め込む記法として、videoという言語名のコードブロックを用意しました。Astroの標準機能ではなく、Markdownを処理するremarkプラグインを実装して追加したものです。videoを指定したコードブロックを見つけると、その中身を読んで表示用のHTMLへ差し替えます。書くのは表示するメディアのURLだけで、VHSの実行や台本からの生成はしません。

```video
src: /videos/astro-vhs-blog/demo.mp4
```

差し替え先のHTMLは、動画ならvideo要素、GIFやPNGならimg要素を含むfigureにしました。VHSで生成したファイルも、別の方法で用意したファイルも、srcで指定すれば同じ表示コンポーネントで扱えます。タイトル、説明、コピー操作、スマートフォン対応もこのコンポーネントにまとめました。

VHSの録画環境を切り替える

VHSの録画環境は、ホスト上で実行する方法と、コンテナで依存関係を固定する方法を選べるようにしました。

手元の検証ではホストのコマンドを使い、インストールなど環境によって手順や結果が変わりやすいコマンドを録画するときは、依存関係を固定したコンテナを使うようにしました。

Vercelのビルドで起きた問題と回避方法

本サイトでは当初、GitHubリポジトリをVercelに接続し、VercelのCI/CD機能をそのまま使ってデプロイしていました。VercelのデプロイではAstroのビルドが自動的に実行され、VHSの生成処理もその中で走る状態でした。ところが、Vercel上でVHSが起動したChromiumがlibnspr4.soを読み込めず、録画に失敗しました。

エラーの原因を調べると、VHSのレンダリングには、仮想ターミナルを動かすttyd、画面を描画するChromium、動画へ変換するffmpeg、Chromiumが読み込むLinuxのネイティブライブラリが必要だと分かりました。Vercelのビルド環境では、録画に必要なこれらのネイティブパッケージをそろえるのが難しいため、Vercel上でVHSを実行する方法を見直しました。

VHSの生成とAstroのビルドをGitHub Actionsの固定したDocker環境で実行し、生成済みの静的ファイルをVercelへPrebuilt Deploymentとして渡す方式に変更しました。録画に必要なVHS、ttyd、Chromium、ffmpeg、Linuxライブラリをコンテナ側へまとめ、GitHub Actionsのキャッシュで再利用します。VercelではVHSを実行せず、完成した静的ファイルだけをデプロイします。

動画・GIF・PNGの表示

この表示コンポーネントには、メディア、説明、再生操作の属性を用意しました。formatで動画プレーヤー、GIF、PNGを切り替え、titlecaptionで説明を追加します。通常のコードブロックや他のMarkdownは、これまでどおり表示するようにしました。

安全に表示できるように、メディアのURLをサイト内のパスまたはHTTPSに限定しました。javascript:data:、制御文字、HTML属性を壊す引用符は受け付けず、記事の値は表示時にエスケープしています。記事の設定が意図しないHTMLとして解釈されないための実装です。

表示するファイルと説明は、AstroのMarkdownファイルで次のように指定します。

```video
format: video
src: /videos/astro-vhs-blog/demo.mp4
webm: /videos/astro-vhs-blog/demo.webm
poster: /videos/astro-vhs-blog/demo.png
title: プロジェクト解析のターミナルデモ
caption: コマンドを実行して解析結果を表示する
```

srcで表示する動画や画像を指定し、titlecaptionで説明を追加します。WebM、ポスター画像、再生できない場合の代替URLも必要な場合に指定できます。

動画の再生操作、繰り返し再生、ミュート、スマートフォンでのページ内再生は既定で有効にし、自動再生は指定した場合だけ有効にしました。

formatで表示形式を切り替えます。

format表示向いている用途
video動画プレーヤー操作UI、ポスター、MP4/WebM、長めのデモ
gif繰り返し再生する画像X向けと同じ短い無音ループ、軽い確認用
png静止画結果の保存、動画のポスター

GIFとPNGでは、altで画像の内容を説明できるようにしました。

GIFの表示例です。

VHSで生成したターミナルアニメーション
GIFは画像として自動的にループします

PNGの表示例です。

VHSで生成したターミナルの静止画
PNGはポスターや静止画として表示します

Blog.astroのCSSを、動画や画像が記事の幅に合わせて表示され、スマートフォンでも横幅に収まるように調整しました。

コピー機能

コピー機能は、動画・GIF・PNGの右上に共通のコピーアイコンを表示するUIとして実装しました。カーソルを重ねると、コピーする内容を「入力をコピー」または「結果をコピー」と表示します。コピー後は一時的に「コピーしました」と表示し、元のラベルへ戻ります。

まず、入力コマンドをコピーする場合は、AstroのMarkdownファイルでvideoブロックにtapeを指定します。台本のTypeから入力コマンドだけを取り出してコピーできるようにしました。

```video
src: /videos/astro-vhs-blog/demo.mp4
tape: tapes/astro-vhs-blog/demo.tape
```

実行結果をコピーする場合は、VHSが出力したテキストファイルをtextで指定します。tapetextを同時に指定した場合は、tapeが優先されて、入力コマンドをコピーします。

VHS生成物の管理例

本サイトでの保存単位の一例は次の通りです。

tapes/
└── <デモ名>.tape

public/videos/<記事スラッグ>/
├── demo.mp4
├── demo.gif
└── demo.png

動画ファイルだけを更新すると、どの手順や設定から作った動画なのか分からなくなります。そこで、台本を残し、同じデモから作ったファイルと一緒に管理しています。

まとめ

VHSの台本からMP4・GIF・PNGを生成し、Astroのvideoコードブロックで記事に表示できるようにしました。formatで表示形式を切り替え、動画エリアのボタンから入力コマンドをコピーできるようにしました。必要な場合は、VHSの実行結果もコピーできます。

今後ともあそぴテックサイトをよろしくお願いします。

参考