YouTube 動画
タイトルだけでは何言ってるのかわからないと思うので、録画して YouTube に上げた。やっているのは、
- fortune コマンドの出力を
- repeat コマンドで繰り返し
- ChatGPT で動的に翻訳して
- 全体をフレームで囲み
- ピングー化して
- スクロール領域を設定して
表示すること。
optex -Mscroll --line 20 -- -Mpingu repeat -i 30 10 fortune --frame --ja
30秒間隔で10回出力しているので、全部見ると6分弱の映像になっている。
以下、ここで使っている技術について解説します。
optex -Mpingu
optex コマンドの -MPingu モジュール。
Pingu 化の元ネタはこちら。メディアでも取り上げられたので見た方も多いと思う。
-Mpingu モジュールは ping だけではなく、あらゆるコマンドを Pingu 化するものだ。最初は pingu コマンドの画像データを拝借してそのまま使っていたのだが、最近各文字を上下に分割して縦方向の解像度を倍増した。
pingu モジュールは、読み込んだデータを1行ずつ、デフォルトでは0.1秒間隔で出力する。
optex -Mscroll
optex の -Mscroll モジュールは、端末にスクロール領域を設定する。デフォルトでは10行だが、この例では --line 20 というオプションを指定して20行の領域を指定している。
docker build コマンドや各種のインストーラーでこのような手法が使われているので、汎用化できないかと実装したものだ。
.optex.d/bin/repeat
repeat コマンドは、その後に指定したコマンドを繰り返し実行する。Cシェルの組み込みコマンドで便利に使っていたのだが bash に切り替えてからなくなって不便に思っていたので optex の alias 機能を使って実装した。-i オプションでインターバルタイムを指定できる。
[alias]
repeat = [ 'bash', '-c', '''
while getopts 'c:i:px' OPT ; do
case $OPT in
c) count=$OPTARG ;;
i) sleep=$OPTARG ;;
p) paragraph=yes ;;
x) set -x ;;
esac
done; shift $((OPTIND - 1))
case $1 in
[0-9]*) count=$1 ; shift ;;
esac
: ${count:=1}
command=("$@")
while ((count--)) ; do
eval "${command[@]@Q}"
if (( count > 0 )) ; then
[ "$paragraph" ] && echo
[ "$sleep" ] && sleep $sleep
fi
done
''', 'repeat' ]
~/.optex.d/bin には repeat という名前で optex へのシンボリックリンクが置いてある。optex は config.toml を読んで上のシェルスクリプトを実行するという仕組みだ。もちろん repeat という名前で同じ内容のスクリプトを置いても構わない。
ただ、こうしておくと optex --rm repeat のようにしてシンボリックリンクを消してしまえばパスから外すことができて便利な場合もある。
.optex.d/bin/fortune
fortune コマンドは homebrew でインストールしたものだが、これも optex を通して実行することで --frame と --ja というオプションを実現している。
option --frame -Mutil::filter \
--of 'ansicolumn -C1 --border=heavy-box -P20 -S85'
define @AREA "(^\S.*\n)+|(^\h+\S.*\n)"
option --ja -Mutil::filter \
--of 'xlate -t JA -sa -o space+ -w80 -e gpt4o -p @AREA'
-Mutil::filter モジュールで --of というオプションが定義されていて、出力フィルターとするコマンドを指定することができる。
ansicolumn
fortune コマンドに --frame オプションを指定すると、出力を ansicolumn -C1 --border=heavy-box -P20 -S85 コマンドを経由して表示する。これは 20x85 の大きさで heavy-box というスタイルのボーダーを付けて1カラムで表示するという指定だ。
xlate
fortune コマンドに --ja オプションを指定すると、出力を xlate コマンドに送る。xlate は bash スクリプトで App::Greple::xlate モジュールの配布に同梱されてるフロントエンドの CLI だ。
ここで指定している xlate のオプションは以下のようなもの。
-
-t JA: 日本語に翻訳 -
-s: 進行状況を表示しない (silent) -
-a: API を使って翻訳する -
-o space+: 原文と翻訳文の後に空白行を出力する形式 -
-w80: 80文字で折り返す -
-e gpt4o: 翻訳エンジンとして ChatGPT-4o を使う -
-p @AREA: 翻訳対象の領域に@AREAを指定する
@AREA は上の行で (^\S.*\n)+|(^\h+\S.*\n) のように定義している。これは、空白以外の文字で始まる行の連続 (^\S.*\n)+ と、空白で始まる行 ^\h+\S.*\n という意味だ。つまり、先頭から始まる文の連続はパラグラフとしてまとめて翻訳し、空白で始まる行は1行ずつ独立して翻訳する。
このパターンを greple コマンドに指定すると、どの部分が翻訳対象になるかがわかる。この例では、連続する行をまとめて翻訳しない方がいいのだが、これを自動的に認識するのは難しい。
fortune の出力は、文章の後にインデントして出典が書かれていることが多いので、このようなパターンでだいたい思ったように翻訳してくれる。もちろん例外はあるので、時々おかしな内容になってしまうこともある。
この例では gpt4o を使って翻訳しているが DeepL を使うことも可能だ。
まとめ
ここ数日 optex の pingu と scroll モジュールを更新していた。このように optex を使って実行するコマンドを連鎖的に利用するのは初めてで、実装を見直す必要があった。greple の xlate モジュールや ansicolumn を組み合わせることで、ある意味集大成的な応用例になっていると思う。
実は、まだどうもうまく動いていない部分がある。
optex -Mpingu fortune --frame
のように実行すると、fortune コマンドの終了を待たずにプロンプトが帰ってきてしまうのだ。
optex -Mpingu env fortune --frame
のように env や sh -c を介して実行すると問題ないので、プロセスグループの管理が絡んでいるのではないかと思っている。原因や対処法がわかる方がいましたらアドバイスお願いします。


