今回はやってみた系
今回は普段と違い、エンターテイナー色強めの記事にしています。
きっかけは友人にハッカソンに誘われたときに見た(未参加)、ハッカソンのダイジェスト動画に感化されたからです。
今回作りたいものは、「キーボード入力の速度に連動して、音楽の再生速度が変化するプログラム」です。
開発環境
- Linux(CentOS7)
- Python3.6
- VLC
# yum install vlc
# pip install 足りないモジュールを随時インストール
大したものは使いません。ただしPythonの中でOS依存のメソッドを使用します(Linux系なら大丈夫)。VLCはコマンド操作で音楽を再生するために使用します。
※VLCは動画再生ソフトとしても非常に強力なソフトです
CLIで音楽再生操作するモジュール
まずはVLC部分のモジュールから。
vlcの操作は以下の通り。
# 引数に音楽ファイルやフォルダを指定して再生
$ vlc ファイル名
# 速度を10%速める・遅める
$ key key-rate-faster-fine
$ key key-rate-slower-fine
だけです。
次に上記のコマンドの内、速度操作のコマンドを記述するファイルを作成します。
echo "\n" > expect.txt
- タイピング速度によってこのファイルの内容を書き換える
- 定期的にこのファイルの内容を読み込んでvlcに流す
今回作成するプログラムを要約すると上の様な流れになります。(このセクションでは2.を実装)
今回は対話処理自動化でお馴染みのexpectを使って実装します。
#!/bin/bash expect -c "
set timeout -1
set OUTFILE_PATH \"expect.txt\"
spawn vlc /home/user/音楽/
sleep 1
send \n
while {True} {
#2秒毎に速度チェックしコマンド決定
set OUTFILE [exec cat \"\${OUTFILE_PATH}\"]
sleep 2
expect -re \">.*\"
send \$OUTFILE
send \n
}
expect eof exit 0
"
言うまでもなく2秒毎にexpect.txtのコマンドを、vlcに垂れ流しています。
タイピング速度検出モジュール
まずはキーボードのデバイスファイルを調べます。
自分の場合だと
$ cat /proc/bus/input/devices
~略~
I: Bus=0003 Vendor=046d Product=c31c Version=0110
N: Name="Logitech USB Keyboard"
P: Phys=usb-0000:00:10.0-1.3/input0
S: Sysfs=/devices/pci0000:00/0000:00:10.0/usb2/2-1/2-1.3/2-1.3:1.0/input/input197
U: Uniq=
H: Handlers=sysrq kbd leds event4
B: PROP=0
B: EV=120013
B: KEY=1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=1f
上のH:行から、次のデバイスファイルが作成されることが分かります。
/dev/input/event4
こいつにキー入力の度に書き込みが行われています。
プログラムを実行するときは、許可を求めらるのがうざいのと、vlcがrootで実行出来ないという都合上、誰にでも参照できるように権限を変更します。
# chmod o+r /dev/input/event4
参照権限なので、大丈夫だとは思うが、念のため遊び終わったら元に戻すことを推奨します。
# バイナリファイル構造体
# struct input_event {
# struct timeval time;
# unsigned short type;
# unsigned short code;
# unsigned int value;
# };
infile_path = "/dev/input/event4"
V_ZERO = 4
count = 0
# thread_1
# キーイベント処理部分
#
import struct
import re
EVENT_FORMAT = "llHHI";
EVENT_SIZE = struct.calcsize(EVENT_FORMAT)
def keyfunc():
global count
with open(infile_path, "rb") as file:
while True:
event = file.read(EVENT_SIZE)
strtmp = struct.unpack(EVENT_FORMAT, event)[3:]
listtmp = strtmp
if listtmp[0] != 0 and listtmp[1] == 0:
count = count + 1
# thread_2
# ラップ処理部分
#
import time
expfile_path = 'expect.txtのパス'
def timerfunc():
global count
btime = time.time()
while True:
ntime = time.time()
if (ntime - btime >= 1):
vel = (count / round(ntime - btime))
if (vel < 3):
with open(expfile_path, mode='w') as f:
f.write('key key-rate-slower-fine\n')
elif (3 <= vel and vel <= 5):
with open(expfile_path, mode='w') as f:
f.write('\n')
else:
with open(expfile_path, mode='w') as f:
f.write('key key-rate-faster-fine\n')
btime = time.time()
count = 0
# main
import threading
if __name__ == "__main__":
thread_1 = threading.Thread(target=keyfunc)
thread_2 = threading.Thread(target=timerfunc)
thread_1.start()
thread_2.start()
スレッド1はキー入力のイベントの際にカウントする処理。スレッド2は1秒毎にタイピング速度(1秒間当たりのタイピング回数)を算出し、vlcコマンドを決定する処理。(expect.txtを書き換えている)
2つのスレッドをそれぞれ、並列に実行しています。
スレッド1について補足すると、キーイベントがデバイスファイルにバイナリとして書き込まれ、それをCの構造体からpython用にパースしています。それらを解析して、特定のキーコード、特定のキーイベント(押す・離す)で評価する。そんなイメージです。
結合
あとは2つのモジュールを繋げます。
key.pyはそのままで、music_expect.shを編集。
#!/bin/bash
# trap処理
trap 'KLPS=`ps -ef | grep key.py | grep -v grep | tr -s " " | cut -d " " -f 2`;kill $KLPS;echo "" > expect.txt' 2 15
python3.6 key.py &
expect -c "
set timeout -1
set OUTFILE_PATH \"expect.txt\"
spawn vlc -I rc /home/user/音楽/
sleep 1
send \n
while {true} {
#2秒毎に速度チェックしてコマンド決定
set OUTFILE [exec cat \"\${OUTFILE_PATH}\"]
sleep 2
expect -re \">.*\"
send \$OUTFILE
send \n
}
expect eof exit 0
"
このままだと、一瞬で再生速度が偏ってしまい、腱鞘炎誘発プログラムとなってしまうので、タイピング速度を検出する式を見直したり、速度の下限を設定するなどして調整する必要がありますが、今回はこれでよしとします。
何はともあれ完成しました。長くなってしまい申し訳ございません。
※下記ファイル構成
.
|-- expect.txt
|-- key.py
`-- music_exprect.sh