変換パイプラインの最適化
端末の録画データには、複数の処理段階にわたって同一の情報が繰り返し現れます。 1 回の画面描画が細切れの PTY(擬似端末)出力に分断され、隣接するフレーム間では大半の文字セルが変化せず、アニメーションでは同一行が何秒にもわたって静止し、動画変換では同じ画面に対するラスタライズが何度も要求されます。 console2svg は後段の描画エンジンを高速化する前に、各変換工程においてこれらの重複を徹底的に削減します。
イベントの到着粒度を整えるコアレッシング
Section titled “イベントの到着粒度を整えるコアレッシング”PTY から読み取った 1 回分のバイト列を、そのまま 1 つの録画イベントとして記録することはありません。 短時間のうちに連続して発生した出力を 1 つにまとめる 出力コアレッシング(Coalescing:小分けに届く入出力を一定の時間窓で束ねる処理)を行い、記録されるイベント数をあらかじめ絞り込みます。
既定のコアレッシング窓は、動画の 1 フレーム時間の 4 分の 1 です。 ただし、描画の即時性を損なわないよう 2 ミリ秒未満には切り下げず、ラグを感じさせないよう 20 ミリ秒を超えない範囲に制限します。 この前処理により、細かなエスケープシーケンスを大量に吐き出して画面を構築する TUI(テキストユーザーインターフェース)ツールであっても、ANSI パーサーが解釈すべきイベント境界を劇的に減らせます。
コピーオンライトによる画面スナップショットの共有
Section titled “コピーオンライトによる画面スナップショットの共有”アニメーションの各フレームを保持するたびに画面全体をディープコピーすると、メモリ消費量は「フレーム数 × 行数 × 列数」の積に比例して爆発します。
ScreenBuffer はこの無駄を排除するため、コピーオンライト(Copy-on-Write:書き込みが発生するまでメモリを共有し、変更時に初めて該当箇所を複製する仕組み)を採用しています。
スナップショットを生成する際は、各行の配列を束ねる外側の配列参照だけを新しく確保し、行実体である ScreenCell[] は元の画面バッファと共有します。
その後、端末エミュレーションの進行によって特定の行に文字の書き換えが発生した瞬間に限り、その行のセル配列だけを新規メモリへ複製します。
この行共有構造は、フレーム間の比較処理でも威力を発揮します。 2 つのスナップショットが同一の行配列インスタンスを参照していれば、その行の全セルを比較するまでもなく「完全に同一」と判定できます。 セル単位の実データ比較に進むのは、配列参照が異なる行だけに限定されます。
差分更新によるビジュアルシグネチャの高速計算
Section titled “差分更新によるビジュアルシグネチャの高速計算”画面や行の同一性を判定する基準として、ビジュアルシグネチャ を用います。 各セルのシグネチャは、表示文字、文字スタイル、全角文字フラグの組み合わせから計算します。 ハッシュアルゴリズムには高速な XxHash3 を採用し、ASCII 文字(0〜127)のシグネチャはあらかじめ事前計算してテーブル化しています。
各行は、列位置情報を含めたセルシグネチャの集合から 64 ビットの行シグネチャを保持します。 シグネチャの追跡を開始した後は、1 文字のセルを書き換えるたびに行全体を再走査することはありません。 変更前のセルの値を XOR 演算で取り除き、新しいセルの値を XOR 演算で追加するだけで行シグネチャを更新できます。
画面全体のシグネチャには 2 種類あります。
カーソルを含まない GetContentSignature はアニメーション本文のフレーム保持判定に用い、カーソルの位置と可視状態を含む GetVisualSignature は動画サンプリングでの完全な描画一致判定に用います。
文字列とスタイルのオブジェクト再利用
Section titled “文字列とスタイルのオブジェクト再利用”端末画面では、同じ文字や同じスタイル属性が広範囲にわたって繰り返し出現します。
console2svg では、ASCII 128 文字分の一文字 string をあらかじめ静的配列として生成しており、セルの文字更新時に char.ToString() によるヒープ割り当てを一切行いません。
文字属性を表す CellStyle インスタンスもバッファ内で共有します。
直前のセルと同じスタイルが連続する一般的なケースでは前回の参照をそのまま代入し、画面内の離れた位置で同じスタイルが再登場した場合は上限 256 件の内部キャッシュから参照を取得します。
真新しい 24bit RGB カラーが連続して流れてきた場合でも、キャッシュの肥大化を防ぐために上限到達時に自動で再構築されます。
SVG 要素と文字配置の集約
Section titled “SVG 要素と文字配置の集約”SVG を出力する際、画面の 1 セルごとに 1 つの要素を出力するような愚直なマークアップは行いません。
同一の背景色が横方向に連続するセルは 1 つの矩形(<rect>)に結合します。
同様に、同一の前景スタイルが続く文字列は 1 つの <text> 要素へ連結します。
文字列の中間に含まれる空白文字は後続の文字へつながる場合のみ保持し、行末の無駄な空白セルは出力から切り捨てます。
全角文字や図形文字が出現した場合は、文字のレイアウト崩れを防ぐために文字列の連結をその境界で適切に区切ります。
<!-- 最適化前(愚直なセル単位描画): セルごとに rect と text を出力(要素数 6、冗長な属性) --><rect x="0" y="0" width="8.4" height="18" fill="#1e1e2e"/><text x="0" y="14" fill="#89b4fa">g</text><rect x="8.4" y="0" width="8.4" height="18" fill="#1e1e2e"/><text x="8.4" y="14" fill="#89b4fa">i</text><rect x="16.8" y="0" width="8.4" height="18" fill="#1e1e2e"/><text x="16.8" y="14" fill="#89b4fa">t</text>
<!-- 最適化後(console2svg の集約処理): 背景と文字列をそれぞれ 1 つの要素へ結合 --><rect x="0" y="0" width="25.2" height="18" fill="#1e1e2e"/><text x="0" y="14" fill="#89b4fa">git</text>さらに、罫線文字(ボックス描画文字)は SVG へ出力する前に水平・垂直の線分ジオメトリへ分解し、同じ色と線幅で接続できる線分を結合したうえで 1 つの <path> データに集約します。
画面全体が罫線で覆われた複雑な TUI ツールであっても、数百個の個別の矩形やフォント文字を数個の連結パスへと集約できます。
<!-- 最適化前: 枠線の交点や区間ごとに多数の微小な破片を出力 --><!-- 最適化後: 同一スタイルの外枠・区切り線を 1 つのパスへ統合 --><path d="M 10 10 H 630 V 200 H 10 Z M 10 40 H 630" stroke="#6c7086" stroke-width="1" fill="none"/>アニメーション行の重複排除と差分化
Section titled “アニメーション行の重複排除と差分化”アニメーション SVG では、保持された全フレームの全画面をそれぞれ出力することはしません。
PrepareAnimatedRows が全フレームの可視行を走査して 行カタログ を作成し、同一内容の行を <defs> 内に 1 回だけ定義します。
さらに、1 行の一部の文字だけが書き換わるタイピングやステータス更新に対しては、直前の行定義を参照して変更範囲だけを上書きする 行差分 を適用します。 差分範囲を 16 列以下かつ行全体の 4 分の 1 以下に制限し、参照深度を 4 段階未満に抑えることで、SVG レンダラーの解決負荷を抑えながら出力サイズを大幅に削減します。
例えば、80 列 × 24 行の端末でコマンドを入力する 100 フレームのアニメーションを想定します。
| 手法 | 出力される行データ | 生成ファイルサイズの目安 |
|---|---|---|
| 愚直な全画面展開(各フレームの全行を出力) | 2,400 行(80×24×100 の全情報) | 約 3.2 MB |
| 行カタログ化(重複行を一意化して参照) | 約 120 行の一意な <g> 定義 | 約 180 KB |
| 行カタログ化 + 行差分(タイピング差分を部分上書き) | 約 35 行の完全定義 + 約 40 個の微小差分 | 約 85 KB |
このように、多層的な重複排除により、画質を一切損なうことなくファイルサイズを 90% 以上削減します。
動画変換における二段キャッシュとパイプ転送
Section titled “動画変換における二段キャッシュとパイプ転送”固定フレームレートによる動画出力では、サンプリング時刻が進むたびに 1 つの端末エミュレーターを順方向へ進め、ビジュアルシグネチャを取得します。
ここで取得したシグネチャに基づき、静止画 SVG 文字列を最大 128 件キャッシュします。
同一画面が連続する場合は XML 文字列の再構築を行わず、同一の string インスタンスを返却します。
動画コンバーターはこの string のオブジェクト参照をキーとして、PNG ラスタライズ結果を最大 16 件キャッシュします。
この 二段キャッシュ により、画面が変化していない時間帯の SVG 再生成と PNG 再ラスタライズの両方を完全にスキップします。
完成した PNG フレームは、ディスク上に一時ファイルとして書き出すことなく、起動中の ffmpeg プロセスの標準入力(image2pipe)へ直接ストリーミングします。
ラスタライズ処理は CPU コア数に応じて最大 8 スレッドで並列実行し、動画本来のフレーム順序を壊さないよう容量制限付き FIFO キューを介して整流転送します。