MYSQLでバイナリデータの取り扱い

MYSQLには慣れているつもりでしたので、対象がバイナリとなっても大したことはないだろうと高を括っていました。

 


 

 #!/bin/bash
dir_SECURE_FILE_PRIV=/var/lib/mysql-files

mysql DATABASE << _SQL_
create table if not exists t_sound(
seq int primary key auto_increment,
DLdate datetime,
estimate datetime,
number int,
fullname varchar(100),
sound mediumblob
);
_SQL_

for ( ( i=701; i<710; i++ ) )
do
cp raw2/$i.raw ${dir_SECURE_FILE_PRIV}
mysql DATABASE << _SQL_
insert into t_sound(number,sound) value($i,load_file('${dir_SECURE_FILE_PRIV}/$i.raw'));
_SQL_
rm ${dir_SECURE_FILE_PRIV}/$i.raw
done

 1)データ型

これは想定内。データ型はBLOB型を使います。特にmediumblob型というのが適当に大きなデータまで取り扱えて使い勝手が良いそうです。

2)insertコマンド

さてバイナリファイルをinsertしようとしてハタと困りました。シェル変数にどうやって読み込もう?

binary=`cat binary_file`

みたいなので読み込めてさえしまえば

insert into table( col1,col2 ) value( $ascii, $binary );

 てな具合にinsertできるのですが、バイナリではそうもいきません。

調べてみるとMYSQLにはload_file関数というのが用意されているそうです。しかし、このload_file関数は次のように制約が多いようです。

3) secure_file_priv値

load_file関数で読み込めるファイルは必ずsecure_file_priv値に示してあるディレクトリ内に存在しないとならないそうです。

secure_file_priv値を調べている画像を下に示します。

f:id:S_E_Hyphen:20171105155945p:plain

 

 使用中の環境では/var/lib/mysql-filesでした。なので必ずこのディレクトリにファイルをコピーしてからload_file関数を使用する必要があります。

my.cnfでsecure_file_priv値は変更できるそうなのですが、ちょっとうまくゆきませんでした。

 

4) データの取り出し

mysql DATABASE << _SQL_ > 701.raw
select sound from t_sound where number=701;
_SQL_

でOKかと思っていたら全然ダメでした。

 


 

#!/bin/bash
dir_SECURE_FILE_PRIV=/var/lib/mysql-files
temp=`mktemp ${dir_SECURE_FILE_PRIV}/XXXX`

rm playback.raw
for ( ( i=701; i<710; i++ ) )
do
rm $temp
mysql nhkradio << _SQL_
select sound into dumpfile "$temp" from t_sound where number=$i;
_SQL_
cat $temp >> playback.raw
done
rm $temp

sox -r 48000 -c 2 -b 16 -e signed-integer playback.raw playback.wav

 select文でinto dumpfileを使用する必要があります。ところが、このdumpfileの書き出し先もまた、先ほどのload_file関数と同じディレクトリでなくてはなりません。

なんだか思っていたよりも、ずっと野暮ったいスクリプトになってしまいました。

いわしのすり身

鰯は頭をとって骨を抜き少し塩をしておきます。

f:id:S_E_Hyphen:20171104135846j:plain

鰯 60グラム  水 40グラム  とろみ剤 少々

という分量でした。

当初、鰯のみでミキサーにかけると身が飛び散ってしまって、すぐにブレードに当たらなくなります。そこで上述の水を加えることで何とか「すり身」にすることができました。ただし殆ど液状で形を保つことができませんので、小さじ1杯分程度とろみ剤を混ぜることで何とか団子状に成型することが可能となりました。

 

f:id:S_E_Hyphen:20171104140502j:plain

沸騰したお湯に適量の醤油を加えて、上述の団子状にした鰯の「すり身」を茹でてみました。

心配していたほどには魚臭さは感じられませんでしたが、お好みでしょうがを加えてみても良いかもしれません。

それよりもフワフワの食感に驚きました。いわゆるツミレとは別物です。

また骨抜きもせずに頭を落としただけの状態のものも試してみました。わずかに骨が感じられる程度で全く問題ありませんでした。カルシウム補給を考えれば骨ごとすり身にする方がお薦めのような気がします。

 

なお鰯は油が大変多く、ミキサーの後片付けがこれまでになく大変だったことも付け加えておきます。

 

10秒間隔にジョブを実行する

当時間間隔にジョブ(drop_jobを想定)を実行する方法として、下記の方法などがよく紹介されています。

#!/bin/bash

while true

do

 sleep 10

 ./drop_job

done 

 これだとdrop_jobが終了した時点から10秒後に次のdrop_jobが再会されます。ですから、例えば

# drop_jobの中身
echo 現在時刻は `date +"%F %T"` です
sleep 5

みたいに、それ自体が時間のかかるジョブだと10秒間隔で実行できません。

manページで見ると、atコマンドに-tオプションを指定すると秒単位まで開始時刻を指定できるみたいなのですが、実際にはこれもうまく行きませんでした。

仕方がないのでC言語で作成してみました。C言語なんて使ったの何年ぶりだろ…。

プログラム名をinterval_jobとして、

 interval_job interval=[実行間隔] job=[実行コマンド] end=[終了日時]

で使用します。終了日時は省略可です。

 今のところ問題なく使用できているようです。

 

f:id:S_E_Hyphen:20171102161306p:plain

 

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<time.h>

void conv_date(time_t time, char *s);
void conv_datetime(time_t time, char *s);
time_t set_time(int year, int month, int day, int hour, int min, int sec);
int usage();
int getparint(int argc, char **argv, char *str, int *ptr);
int getparfloat(int argc, char **argv, char *str, float *ptr);
int getparstr(int argc, char **argv, char *str, char *ptr);

/***************** self documentation *********************/
char sdoc[20][80] = {
" ",
" interval_job 等しい秒間隔にジョブを投入する ",
" option: ",
" interval= 実行間隔(秒) ",
" job= 実行するコマンド(80文字以内) ",
" end= 終了日時(YYYY-MM-DD hh:mm:ss) ",
" "};
/***************** end self doc ***************************/

main(int argc, char **argv)
{
int interval;
char job[80];
time_t now,base,end;
char datetime[30],endtime[30];
int year,month,day;
int hour,min,sec;
if(!getparint(argc,argv,"interval",&interval))usage();
if(!getparstr(argc,argv,"job",job))usage();
if(!getparstr(argc,argv,"end",endtime))sprintf(endtime,"2038-01-18 23:59:59");
sscanf(endtime,"%4d-%2d-%2d %2d:%2d:%2d",&year,&month,&day,&hour,&min,&sec);
end=set_time(year,month,day,hour,min,sec); // 終了日時のtime_tを取得

time(&base); // 開始日時のtime_tをbaseとする
while(base<end)
{
while(now<base){time(&now);} //ここがタイマー
system(job); // ループを抜けたらジョブを実行
base+=interval;
}

}

void conv_date(time_t time, char *s)
{
struct tm *lst;
lst=localtime(&time);
strftime(s,256,"%Y-%m-%d",lst);
}

void conv_datetime(time_t time, char *s)
{
struct tm *lst;
lst=localtime(&time);
strftime(s,256,"%Y-%m-%d %H:%M:%S",lst);
}

time_t set_time(int year, int month, int day, int hour, int min, int sec)
{
struct tm tm_ptr;
tm_ptr.tm_year = -1900 + year;
tm_ptr.tm_mon = -1 + month;
tm_ptr.tm_mday = day;
tm_ptr.tm_hour = hour;
tm_ptr.tm_min = min;
tm_ptr.tm_sec = sec;
if(mktime(&tm_ptr)==(time_t)-1)
{
fprintf(stderr,"NG! set_time\n");
} else {
return(mktime(&tm_ptr));
}
}


int usage()
{
int i;
for(i=0;i<20;i++)
fprintf(stderr,"%s\n",sdoc[i]);
exit(1);
}
int getparint(int argc, char **argv, char *str, int *ptr)
{
int i,len;
char s1[50];
len=strlen(str)+1;
for(i=1;i<argc;i++)
{
if(!strncmp(str,*(argv+i),strlen(str)))
{
strcpy(s1,*(argv+i));
sscanf(s1+len,"%d",ptr);
return(1);
}
}
return(0);
}
int getparfloat(int argc, char **argv, char *str, float *ptr)
{
int i,len;
char s1[50];
len=strlen(str)+1;
for(i=1;i<argc;i++)
{
if(!strncmp(str,*(argv+i),strlen(str)))
{
strcpy(s1,*(argv+i));
sscanf(s1+len,"%f",ptr);
return(1);
}
}
return(0);
}
int getparstr(int argc, char **argv, char *str, char *ptr)
{
int i,len;
char s1[50];
len=strlen(str)+1;
for(i=1;i<argc;i++)
{
if(!strncmp(str,*(argv+i),strlen(str)))
{
strcpy(s1,*(argv+i));
strcpy(ptr,s1+len);
return(1);
}
}
return(0);
}

 

 

 

GMT ver5でマイク入力を描画

マイク入力を録音するには arecord コマンドを使用します。音質にこだわる訳ではありませんので、サンプリングレートは2000Hzとしました( -r 2オプション)。分解能は16bitでもよかったのですが、なぜか符号なし8bitとなってしまいました。5秒間分をraw形式で出力します。

arecord -t raw -r 2 -d 5

出力されたバイナリファイルをodコマンドでテキスト形式に変換します。 -w1 オプションは1行に1バイト(8bit)分書き出すことを示します。 -t u1 オプションで符号なし8bitとして取扱います。-An はオフセットアドレスの非表示を、 -v オプションは繰り返し省略の無効を示します。

od -w1 -t u1 -v -An

awk コマンドで整形します。行数をサンプリングレートで割って秒に変換しています。またodコマンドの出力から128を引いて静音時がゼロとなるようにしています。この結果を一旦一時ファイルに書き出します。

awk '{print NR/2000,$1-128}' > $temp

横軸25センチ縦軸10センチで線形投影します(-JX25c/10c)。

横軸は0秒から5秒まで、縦軸は-50から+50までとします(-R0/5/-50/50)。

横軸は1秒ごとに目盛を示し”sec”というラベルを付けます(-Bxa1+l"sec")。

縦軸は10ごとに目盛を付けます。ラベルは表示しません(-Bya10 )。

グラフの線幅は0.5ポイントとします(-W0.5p)。

gmt psxy $temp -JX25c/10c -R0/5/-50/50 -Bxa1+l"sec" -Bya10 -V -W0.5p > ${img}.ps

 約1秒間隔で「イチ、ニ、サン、シ、ゴ」と話した結果の例です。

f:id:S_E_Hyphen:20171101170950j:plain

 


#!/bin/bash
temp=`mktemp ./XXXX`
img="音声"
arecord -t raw -r 2 -d 5 |\
od -w1 -t u1 -v -An |\
awk '{print NR/2000,$1-128}' > $temp
gmt psxy $temp -JX25c/10c -R0/5/-50/50 -Bxa1+l"sec" -Bya10 -V -W0.5p > ${img}.ps
rm $temp
convert -density 300 ${img}.ps ${img}.jpg

 

 

 

不定形封筒の宛名書き

プリンターが取り扱うことができる紙サイズの最小値よりも、さらに小さな不祝儀袋を買ってきてしまいました。

表書きができず途方に暮れていたのですが、不要な長型3号封筒に切り込みを入れ、そこに不祝儀袋を挟み込むことで無事目的を達することができました。

f:id:S_E_Hyphen:20171025154637j:plain

スピーカーの音量を変更する

PulseAudioスピーカーの音量を変更するにはpacmdコマンドを使用するのですが、すぐにデバイス番号などを忘れてしまったりするので、ボリューム変更に特化したシェルスクリプト set-volume を作成してみました。

 


 

#!/bin/bash
temp=`mktemp XXXX.tmp`
target="alsa_output.pci-xxxx"
pacmd list-sinks |\
grep -B 1 -A 10 $target > $temp
index=`cat $temp |\
grep index |\
sed -e "s/^.*://g"`
left_volume=`cat $temp |\
grep front-left: |\
sed -e "s/^.*front-left://g"|\
awk '{print $1}'`
right_volume=`cat $temp |\
grep front-right: |\
sed -e "s/^.*front-right://g"|\
awk '{print $1}'`
echo index=$index
echo 前の音量=$left_volume $right_volume
printf "変更後の音量:"; read volume
pacmd set-sink-volume $index $volume
rm $temp 

 

alsa_output.pci-xxxxというデバイスを使用しているとします。

pacmd list-sinks コマンドで全てのステータスをリストし、文字列"alsa_output.pci-xxxx"を含む行の前1行、後10行をgrep -B -A コマンドで一時ファイルに書き出します。

この一時ファイルからindexとvolumeに関係する部分を検索し、表示しています。

 

端末でset-volumeと叩くとデバイス番号(ここでは1)と現在の音量(30000)が表示され、変更後の音量を入力できるようになります。数値を入力すると(32000)、pacmd set-sink-volumeコマンドによって音量が変更されます。

 

f:id:S_E_Hyphen:20171018111722p:plain

 

梅干寒天と梅干ゼリー

450ccの水に砂糖大さじ1を加え、沸騰させてから粉末寒天1包(4g)を1~2分間よく煮溶かします。寒天は十分に煮立たせることがポイントだそうです。

寒天液100ccに対して梅酢20g(カンロレードル1杯分)を加え、ゼリー型に流し入れ固めます。梅干し1個もあしらいます。

f:id:S_E_Hyphen:20171012134542j:plain

非常に強い酸味で、お茶うけのみならず、ご飯のおともにも使えそうです。

 

一方、水200ccに梅酢40gを加えて煮立たせます。

これに砂糖大さじ1杯と粉ゼラチン1包(5g)を加え、ゼリー型に流し入れ冷やしました。

粉ゼラチンはなかなか固まらないので心配になりました。ゼラチンは寒天とは違い80℃程度が適温なのだそうで、煮立たせるとよくないそうです。

それでも冷蔵庫で5時間程度冷やしておくと、なんとか固体になってくれました。

固まってさえくれるのであれば、ゼラチンの方が使い易いし、食感も好みに合います。

これからは季節もよくなりますので、ゼラチンを多用してゆきたいと思っています。