---
title: ターミナルに動画を表示してみよう
tags: 
author: [murasuke](https://docswell.com/user/4962106)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/4EZLLXKG73.jpg?width=480
description: ターミナルに動画を表示してみよう by murasuke
published: May 01, 26
canonical: https://docswell.com/s/4962106/5E1LNE-2026-05-01-080619
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/4EZLLXKG73.jpg)

ターミナルに動画を表示してみよう
～ エスケープシーケンスで
動画の表示にチャレンジ ～
2025/09/22 俺たちの勉強会 #4
#orestudy


# Page. 2

![Page Image](https://bcdn.docswell.com/page/Y76WW4QQ7V.jpg)

自己紹介：murasuke
・株式会社 ツールラボ 開発部 所属
PHPで自社Webサービスの開発をやっています
・ボイジャー2号より後、１号より前にうまれました
・役に立たたない(けど面白い)技術を考えるのが好きです


# Page. 3

![Page Image](https://bcdn.docswell.com/page/G75MMQR274.jpg)

早速ですが動画を表示してみます
$ ./terminal_movie.sh


# Page. 4

![Page Image](https://bcdn.docswell.com/page/9J299PD1ER.jpg)

ターミナルで動画を再生する仕組み
● (見ての通り) 単なるパラパラ漫画でした
● ターミナルに「ドット単位」で色を出力する`Sixel Graphics`
というエスケープシーケンス を利用しています
●
リアルタイムでパラパラ漫画にするのは重すぎるので、
事前に変換しています
まずは、 `Sixel Graphics`とは何か？について説明します


# Page. 5

![Page Image](https://bcdn.docswell.com/page/DEY44524JM.jpg)

`Sixel Graphics`とは？
●
Sixel Graphicsは、特別なエスケープ
シーケンスをターミナルに送信すること
で、画像を表示する技術です
●
1文字で縦に 6ピクセル分出力します
●
出力位置を右、下にずらしながら描画を
繰り返すことで、画面全体に色を表示す
ることができます


# Page. 6

![Page Image](https://bcdn.docswell.com/page/VJNYYN8678.jpg)

`Sixel Graphics`のデータ構造について
1. 全体の構成は下記のようになっています
“Sixel開始シーケンス,アスペクト比,解像度の指定,カラーパレットの定義,Sixel
データ文字,終了シーケンス”
カラム名
文字シーケンス
補足
Sixel開始シーケンス
\x1BPq
ESC(\x1B) + &#039;Pq&#039;
アスペクト比
&quot;1;1;
アスペクト比1:1
解像度の指定
96;96
解像度96dpi x 96dpi
カラーパレット
※後述
色番号(0～255)と色を定義
Sixelデータ文字
※後述
終了シーケンス
\x1B\
ESC(\x1B) + &#039;\&#039;


# Page. 7

![Page Image](https://bcdn.docswell.com/page/YE9PPRKRJ3.jpg)

Sixelデータ文字について
● Sixelデータ文字 は、 ? (0x3F) から ~ (0x7E) の範囲の文字です。
直前で指定された色で、縦に 6ピクセル分の出力を行います。
`@`は上下6ピクセルのうち一番上
`~`は全体を塗りつぶします
※塗りつぶしをしないピクセルは
`透明`扱い
6ピクセル別々の色を出力するためには、描画が重ならない文字
`@` ⇒ `A` ⇒ `C` ⇒ `G` ⇒ `O` ⇒ `_` の順に(上書き)描画します


# Page. 8

![Page Image](https://bcdn.docswell.com/page/GE8DDWRGED.jpg)

2×2の画像を描画してみよう
1.
2.
４色分のカラーパレットを準備する
色の選択＋描画（#1(色番号)@(Sixelデータ文字)）
#1@#2@$#3A#4A
・カラーパレット定義
#1(色番号);2(RGB指定);(red;green;blue)
#1;2;100;0;0
#2;2;0;0;100
#3;2;0;100;0
#4;2;100;100;100


# Page. 9

![Page Image](https://bcdn.docswell.com/page/LELMMNKX7R.jpg)

2×2の画像を描画はこれです
“\x1BPq&quot;1;1;96;96#1;2;100;0;0#2;2;0;0;100#3;2;0;100;0#4;2;100;100;100#1
@#2@$#3A#4A$\x1B\”
カラム名
文字シーケンス
補足
Sixel開始シーケンス
\x1BPq
ESC(\x1B) + &#039;Pq&#039;
アスペクト比
&quot;1;1;
アスペクト比1:1
解像度の指定
96;96
解像度96dpi x 96dpi
カラーパレット
#1;2;100;0;0#2;2;0;0;100#3;2;0;100; #色番号(0～255)と色を定義
0#4;2;100;100;100
Sixeデータ文字
#1@#2@$#3A#4A$
#色番号+Sixelデータ文字
$は行頭へ戻る
終了シーケンス
\x1B\
ESC(\x1B) + &#039;\&#039;


# Page. 10

![Page Image](https://bcdn.docswell.com/page/4JMYYX23JW.jpg)

それでは変換プログラムを作りましょう
Sixel Graphicsは完璧に理解できましたね！！！
1. 動画を画像として変換します
⇒ ffmpegを使って、パラパラ漫画化します
（ブラウザでパラパラ漫画化できますが、ここは手抜き)
2. 画像データを、 `Sixel Graphics` 形式の文字
(エスケープシーケンス＋データ )に変換します
3. Sixel形式に変換した文字列を echoでひたすら表示します
⇒ 動画っぽく表示されます


# Page. 11

![Page Image](https://bcdn.docswell.com/page/PJR99NMR79.jpg)

動画から画像を切り出します
1. ffmpegを使って変換
(scene_001.png、scene_002.png ・・・という連番で保存されます）
# 画像への変換 fps: フレームレート -t: 秒数
$ ffmpeg -i &quot;$FILE&quot; -vf fps=$FPS -t $TIME ./scene_%03d.png &gt; /dev/null 2&gt;&amp;1


# Page. 12

![Page Image](https://bcdn.docswell.com/page/PEXQQNVYJX.jpg)

変換プログラムを書いてみましょう(TypeScript)
●
画像のパスを渡したら「Sixel Graphics」に変換するプログラムです
画像読んで、減色処理して、変換するだけです
№
関数名
概要
1
main()
下記処理を呼び出すメイン関数
2
imageLoader()
画像ファイルを読み込み、Canvasを使用して
そのピクセルデータを取得
3
reductionColor()
画像の色を216色のWebセーフパレットに減色
4
convertToSixel()
減色されたデータをSixel Graphics文字列に変換


# Page. 13

![Page Image](https://bcdn.docswell.com/page/3EK99NQ4ED.jpg)

１. main()関数
各処理を呼び出すだけ
import { loadImage, createCanvas, Image } from &#039;canvas&#039;;
// 画像読み込みとピクセルデータ取得
const { data, img } = await imageLoader(ﬁlename);
// 減色処理: 各ピクセルの色を減色して、パレット番号に変換する
const { colorData, colorMap } = reductionColor(data, img.width, img.height);
// Sixelグラフィックス(エスケープシーケンス)文字列に変換
const sixel = convertToSixel(colorData, colorMap, img.width, img.height);
// コンソールに表示
console.log(sixel);


# Page. 14

![Page Image](https://bcdn.docswell.com/page/L73WWVPD75.jpg)

２. imageLoader()
画像をロードして、ピクセルの色情報を返します
// 画像読み込み
const img = await loadImage(ﬁlename);
// 画像と同じサイズのCanvasを作成し、画像を描画する
const canvas = createCanvas(img.width, img.height);
const ctx = canvas.getContext(&#039;2d&#039;);
ctx.drawImage(img, 0, 0);
// Canvasからピクセルデータを取得
const data = ctx.getImageData(0, 0, img.width, img.height).data;
return { data, img };


# Page. 15

![Page Image](https://bcdn.docswell.com/page/87DKK8L2JG.jpg)

３. reductionColor()
カラーパレットの色数制限(256色)に合わせて、216色(6×6×6)に減色処理します
// ピクセルデータの配列(左上からピクセル毎にパレット番号をセット)
const colorData = new Uint32Array(width * height);
// 色を一意に識別するためのMap (RGB値 -&gt; パレット番号)
const colorMap = new Map&lt;number, number&gt;();
// sixelでは0～100の範囲で指定する(2.55だと少しくらいため微調整)
const scaleFactor = 1 / 2.3;
// 0～100を6段階に(RGBで216色)分割する場合の量子化ステップ(100/6=約16)
const quantize = 16;
for (let i = 0; i &lt; width * height; i++) {
const offset = i * 4;
const a = data[offset + 3]; // アルファ値
// 各色成分のMaxを100に調整
const scaledR = data[offset] * scaleFactor;
const scaledG = data[offset + 1] * scaleFactor;
const scaledB = data[offset + 2] * scaleFactor;
// 各色成分を quantize の倍数に丸める
// （ 0, 16, 32, 48, 64, 80）
const qR = Math.ﬂoor(scaledR / quantize) * quantize;
const qG = Math.ﬂoor(scaledG / quantize) * quantize;
const qB = Math.ﬂoor(scaledB / quantize) * quantize;
// 3色を1つの数値にまとめる（24bit RGB値として扱う）
let qRGB = qR * 256 * 256 + qG * 256 + qB;
// 透明ピクセルは白として扱う
if (a === 0) { qRGB = 0xffffff; }
// 存在しない色なら、新たなパレット番号を割り当てる
if (!colorMap.has(qRGB)) {
colorMap.set(qRGB, colorMap.size + 1);
}
// 現在のピクセルに対応するパレット番号を記録する
colorData[i] = colorMap.get(qRGB) ?? 0;
}
return { colorData, colorMap };


# Page. 16

![Page Image](https://bcdn.docswell.com/page/VJPKK82LE8.jpg)

４. convertToSixel()
減色されたデータをSixel Graphics文字列に変換します
const ESC = &#039;\x1B&#039;;
let output = ESC + &#039;Pq&#039;; // Sixel開始シーケンス
// 画像のプロパティ指定（アスペクト比1:1、解像度96dpi x 96dpi）
output += `&quot;1;1;96;96`;
// カラーパレットの定義
// Map.forEach のコールバックは (value, key) の順で渡される
colorMap.forEach((paletteIndex: number, quantizedRGB: number) =&gt; {
// quantizedRGB から各色成分を抽出
const r = (quantizedRGB &gt;&gt; 16) &amp; 0xff;
const g = (quantizedRGB &gt;&gt; 8) &amp; 0xff;
const b = quantizedRGB &amp; 0xff;
output += `#${paletteIndex};2;${r};${g};${b}`;
});
// ここでは、各ピクセルのパレット番号と、6ピクセルブロックの
// ビットパターンを表すキャラクタ（仮の例として行番号により決定）を出力する
// 6段のビットパターンを表現する文字群
const chars: string[] = [&#039;@&#039;, &#039;A&#039;, &#039;C&#039;, &#039;G&#039;, &#039;O&#039;, &#039;_&#039;];
let i = 0; // ピクセルデータのインデックス
// 画像の各行を処理するループ
for (let y = 0; y &lt; height; y++) {
for (let x = 0; x &lt; width; x++) {
// 各ピクセルごとに、対応するパレット番号とビットパターンを出力
output += `#${data[i]}${chars[y % 6]}`;
i++;
}
// 行の終わりでキャリッジリターンを出力。
// 6行ごとに &#039;-&#039; を付加して次のブロックに移動。
if (y &gt; 0 &amp;&amp; (y + 1) % 6 === 0) {
output += &#039;$-&#039;;
} else {
output += &#039;$&#039;;
}
}
output += ESC + &#039;\\&#039;; // Sixel終了シーケンス
return output;


# Page. 17

![Page Image](https://bcdn.docswell.com/page/2EVVVND6EQ.jpg)

Sixel変換スクリプトを実行してみましょう
png画像を変換
⇒ ターミナルに
画像が表示されました
エスケープシーケンスを含む文字を出力しているだけなので、リダイレクトしてファイルに
保存したファイルを、ループでechoすればパラパラ漫画になります


# Page. 18

![Page Image](https://bcdn.docswell.com/page/57GLLKY2EL.jpg)

ターミナルで動画を表示した件のまとめ
●
最近のターミナルは画像も表示できる(任意のドットに色を指定できる)
Windows Terminal、XTermなど
●
画像を表示するには非効率だけど、マシンパワーのおかげで
パラパラ漫画を表示するくらいはできるようになった
（最適化していなくても）
●
SSH経由で表示することも可能。単に文字をechoしているだけなので
(ネットワーク速度が重要)
以上です、ありがとうございました。


