pythonでMP3タイトルを一括変更する

森沢洋介著「瞬間英作文」。素晴らしいですね。英語で会話するときに言いたいことがあるけれど、文章にできずに簡単にしか言えませんでしたが。この本をきっかけに打開できてきたような気がします。付録CDをiPhoneに入れて、ミュージックアプリで通勤途中に聞いています。日本語を聞いて、英語の文章を作って、英語の音声と比べてみる。というのを繰り返して、長い文書も段々と自然と作れるようになってきました。この学習も次の段階で、話すための瞬間英作文シャッフルトレーニングを買ってやろうとしています。

前置きは長くなりましたが、今回は瞬間英作文シャッフルトレーニングのCDをMP3にしてiPhoneへ転送したところで、意外な苦労をしたので書きます。

問題1 アルバムのタイトルがまとまらない

何故かアルバム名がたくさんできてしまいました。アルバム名を揃えたり短くしたりいろいろやってみましたがうまくいきませんでした。結果的に、いったんmp3ファイルを1つだけ転送してから、残りのファイルを転送するとうまくいきました。下記のブログにずばり答えが書いてあったので助かりました。
iPadのMusicアプリ上でアルバムがまとまらない

またMacでMP3のアルバム名やタイトルなどをGUIで設定したい場合はID3 Editorが利用できます。アルバム名やジャンルなど共通の項目は複数のファイルをまとめて編集することができます。

問題2 曲名が長すぎてはみ出してしまう

これは曲名をすべて半角に変更して対応しました。ID3 Editorで曲名は一つずつ編集するしか無いのでコマンドラインですることを考えました。ID3 Editorでもコマンドラインのツールが有るようですが、やり方があまり詳しく乗っていないので、今回はpythonでライブラリを読み込んでやることにしました。まず作成したコードは

mutagenというライブラリを使用しています。ライブラリのインストールは

pip install mutagen

全角半角の変換はjaconvライブラリを使用しています。これもライブラリのインストールが必要です。

pip install jaconv

.replace(‘Part’,”)でPartという文字を消していますが、これは、CD2のタイトルがPart1と2に分かれていて、これがあるとiPhoneのミュージックアプリで見たときはみ出してしまうので削除しました。編集した結果下記のように見やすくなりました。

ASIO C++ UDPクライアントをモジュール化

前回まで三菱MCプロトコルのライブラリをつくるため通信テストをしてきましたが、プロクラムの中にASIOの記述をずらずらと書いていたのでクラスにまとめてモジュール化しようと考えています。

クラスにするだけでも、結構苦労しました。特にsocketをローカル変数宣言して、コンストラクタで設定するところです。構造体のデータを転送してみましたが、エラーが発生してうまく行かなかったので、まずはnetcatで立ち上げたローカルサーバへの送信プログラムを作ってみました。プログラムはgistへアップしましたので参照してください。
UDPClient

早速説明していきます。UDPClientクラスの変数はendpointとsocketを宣言します。コンストラクタではio_contextとIPアドレスとポート番号を受けて、socketとendpointを初期化しています。

class UDPClient
{
private:
	asio::ip::udp::endpoint endpoint;
	asio::ip::udp::socket socket;

public:
	UDPClient(
		asio::io_context& io_contex, 
		const std::string host, 
		const short port)
		: socket(io_contex)
	{
		asio::ip::udp::endpoint endpoint_(asio::ip::address::from_string(host), port);
		endpoint = endpoint_;
	}

UDP送信は構造体で送信することを考えて、テンプレートで作成しました。(まだ実際に構造体での転送は試していません。)

template <typename T>
void Send(T msg,int size)
{
	socket.send_to(asio::buffer(&msg, size, endpoint);
}

mainの中は次のとおりです。io_contexとIPアドレスとポート番号を指定してクラスを宣言し、キーボードで入力した文字をUDPサーバへ送信するだけのプログラムです。

asio::io_context io_context;
const std::string host = "127.0.0.1"; 
const short port = (short)60000;

UDPClient udp(io_context,host,port);
udp.Open();    
for(;;)
{
    std::string inmsg;
    getline(std::cin, inmsg);        
    if(inmsg=="q") break;
    inmsg+="\r\n\r\n";
    udp.Send(inmsg,inmsg.size());
}
udp.Close();

私のMacbook環境ではnetcatが使えるのでnetcatが使えるので、netcatをUDPサーバ、ポート番号60000で立ち上げて、文字が表示することを確認しました。

$ netcat -l -u -p 60000

メインのプログラムでinmsg+=”\r\n\r\n”;としているのは、netcatでうまく開業されなかったからです。他に1文字か2文字だと一段多く改行したり、十何文字か多く打つと文字が変になったりするのですが、とりあえずモジュール化して通信できるました。

次は受信についてやり、構造体のデータを送ります。

三菱PLC通信 構造体でトライ -コマンド受信

前回に続き、今回は構造体でコマンドを設定しての受信について説明します。
まずは受信の構造体からです。

//受信コマンドヘッダ
struct QNa3E_Frame_Receive{
    std::uint16_t subheader;
    std::uint8_t network_number;
    std::uint8_t pc_number;
    std::uint16_t request_IO_number;
    std::uint8_t request_station_number;
    std::uint16_t response_data_length;
    std::uint16_t terminal_code;
}rsv;

これも、リファレンスマニュアルのQnA互換3Eフレームのバイナリコードで更新する場合を見ながら作成しました。UDP受信については前々回の三菱PLCとイーサネット通信をご参照ください。

// UDP受信
std::array<unsigned char, 128> recv_buf;
asio::ip::udp::endpoint sender_endpoint;
size_t recv_buf_len = socket.receive_from(
    asio::buffer(recv_buf), sender_endpoint);

今回はこの受信バッファを構造体に入れます。

// 受信データを受信ヘッダへコピー
int rsv_size = sizeof(QNa3E_Frame_Receive);
memcpy(&rsv, &recv_buf,rsv_size);

構造体のサイズ分memcpyでコピーしています。

// 受信したデータを書き出し
for(size_t i=rsv_size; i<recv_buf_len; i+=2)
    std::cout  << (recv_buf[i + 1] << 8) + recv_buf[i] << std::endl;

受信したデータを標準出力で出力します。受信バッファrecv_bufには1バイトずつデータが入っているので、1ワードずつ出力するには、forループで2つずつ進めて上位バイトを8bitシフトさせて下位バイトを足して、ワードデータにして表示しました。出力は下記のようになります。

1
10
100

構造体で作成することができました。次回からはMX Componentの関数にあるような感じでクラスを作っていきます。

三菱PLC通信 構造体でトライ -コマンド送信

前回の記事三菱PLCとイーサネット通信では、文字列に書いたコマンドを送信して受信データを文字列で表示しましたが、今回はそれを構造体にして書いてみます。

コードはまたgistsにアップします。
melsec_struct_test.cpp

それではコードを説明します。まずは送信コマンドのヘッダの構造体です。

#pragma pack(push,1)

//送信コマンドヘッダ
struct QNa3E_Frame_Send{
    std::uint16_t subheader=0x0050;
    std::uint8_t network_number=0x00;
    std::uint8_t pc_number=0xFF;
    std::uint16_t request_IO_number=0x03FF;
    std::uint8_t request_station_number=0x00;
    std::uint16_t request_data_length;
    std::uint16_t cpu_monitoring_timer=0x10;
} qna;

pragma pack(push,1)を入れることにより、バイナリで転送したときの間を開けずに詰める事ができます。もし入れない場合は「std::uint8_t request_station_number=0x00; //要求先ユニット局番号」の後に00が入ってしまいます。構造体宣言の最後にはpragma pack(pop)と書きます。

構造体はリファレンスマニュアルのQnA互換3Eフレームのバイナリコードで更新する場合を見ながら作成しました。

次に一括読み出しの構造体です。

//一括読み出しコマンド
struct ReadAllCommand{
    std::uint16_t command=0x0401;
    std::uint16_t sub_command=0x0000;
    std::uint32_t top_device;
    std::uint16_t number_of_device;
} read_all;

デバイスはDが0xA8、Mが0x90と値が決められているので列挙体で作成しました。std c++11からは列挙体の基礎となる型を指定できます。

//デバイス
enum struct Device_type : std::uint32_t{
    D = 0xA8,
    X = 0x9C,
    Y = 0x9D,
    M = 0x90,
};

次にmain関数に移ります。まずはデバイス番号を設定します。前回の例と一緒でD300から3ワードという設定です。

/デバイス番号を設定
std::uint32_t device = static_cast<std::uint32_t>(Device_type::D);

列挙体ではキャストして変数へ格納します。

read_all.top_device = ( device << 24) | 300;  // D300
read_all.number_of_device = 3; // 3ワード

デバイスは4バイトのうち先頭の1バイトがDとかMなどの種類で、残り3バイトがアドレスになります。列挙体で格納したデータを3バイト分(24bit)シフトしました。デバイス数はそのまま数値を入力します。

qna.request_data_length=sizeof(qna.cpu_monitoring_timer) + sizeof(read_all)

で、要求データ長を計算しています。
次にUDP送信です。

// UDP送信
std::string const host = "192.168.0.10";
short const port = 1025;
asio::io_context context;
asio::ip::udp::endpoint endpoint(
    asio::ip::address::from_string(host), port);
asio::ip::udp::socket socket(context);
socket.open(asio::ip::udp::v4());
socket.send_to(asio::buffer(&qna, sizeof(qna)), endpoint);
socket.send_to(asio::buffer(&read_all, sizeof(read_all)), endpoint);

ASIOについては前回の記事を参照してください。send_toで送信するところは、送信ヘッダと受信コマンドを構造体毎に送っています。

レスポンスの受信については次回説明します。

三菱PLCとイーサネット通信

またまた久しぶりの更新になってしまいました。今回のテーマは三菱PLCとイーサネット通信。将来的にはRaspberry Piなどの非WindowsのからPLCへアクセスしたいと考えています。

まずは通信の確認。Perlで通信確認をしているブログがあるのでこれを参考にしました。Raspberry Piで動作させるならPerlでも十分なのですが、マイコンでも動作させることを考えて、C++で作成しました。

このサイトを参考ししています。

Perlで三菱シーケンサーとSocket通信

出来上がったプログラムはGistに載せました。

melsec_test.cpp

やっていることは、UDP接続でD300~D302のデータメモリを受信をリクエストして、返答を受信するという内容です。基本的に参考にしたサイトの内容をC++に置き換えているだけですが、通信方式はUDPにしたところが違います。

環境ですが、MacbookにUSB接続優先LANでPLCと接続しています。UDP通信にはAsio C++ Libraryを使っています。Asioはhppファイルのみで構成されているので、ファイルをダウンロードして、適当なフォルダに移して、コンパイル時にインクルードするだけで使用できます。コンパイルはこのような感じに。

clang++ -std=c++11 -stdlib=libc++ -g -O0 -W -I../../source/asio-1.12.2/include melsec_test.cpp -o melsec_test

プログラムの内容について説明していくと

Boostに依存したくない場合はインクルードする前に、ASIO_STANDALONEマクロを実行する必要があるとのこと。

#define ASIO_STANDALONE
#include <iostream>
#include <asio.hpp>

テストなのでMCプロトコルの受信コマンドを文字列で定義します。コマンドの詳細については「Perlで三菱シーケンサーとSocket通信」を確認してください。

// 送信コマンド
std::string cmd = "500000FFFF03000C001000010400002C0100A80300";
std::cout << "send: " << cmd << std::endl

array配列を作って、文字列をバイナリデータに変換しています。perlだとpack関数があるようですが、ここでは自分で作りました。(もしC++に便利なライブラリがあるのなら教えていただきたいです。)

//16進数アスキーを数値に変換
char hex2char(char h)
{
    if (h >= '0' && h <= '9')
        return h - '0';
    else if (h >= 'A' && h <= 'F')
        return h - 'A' + 10;
    else if (h >= 'a' && h <= 'f')
        return h - 'a' + 10;
    return 0;
}

文字を一文字ずつ数値に変換して、char一つについて2つの数値が入るように詰めて配列に入れています。16進数の文字列を0〜15に変換する関数も作りました。

// 送信コマンドをバイナリデータへ変換
std::array<unsigned char, 128> send_buf;

int cnt;
for (size_t i = 0; i < cmd.size(); i += 2)
{
    cnt = i / 2;
    send_buf[cnt] = (hex2char(cmd[i]) << 4) + hex2char(cmd[i + 1]);
    ;
}
int send_buf_len = cnt + 1;

ASIOの送信部分です。非同期I/Oオブジェクトを作って、送信先のアドレスとポートをエンドポイントに設定し、ソケットを開き、データをバッファに入れて送信。という流れです。ASIOについてはあまり理解していないので引き続き勉強が必要です。

// UDP送信
std::string const host = "192.168.0.10";
short const port = 1025;
asio::io_context context;
asio::ip::udp::endpoint endpoint(
    asio::ip::address::from_string(host), port);
asio::ip::udp::socket socket(context);
socket.open(asio::ip::udp::v4());
s

ちなみにTCP送信のときは次のようにすればできます。(PLC側にTCPポートを1026に登録)

// TCP送信
std::string const host = "192.168.0.10";
short const port = 1026;
asio::io_context context;
asio::ip::tcp::endpoint endpoint(
    asio::ip::address::from_string(host), port);
asio::ip::tcp::socket socket(context);
asio::connect(socket,&endpoint);
socket.write_some(asio::buffer(send_buf, send_buf_len));

次にUDPからの受信。送信とは逆に配列を作って、受信バッファから配列に入れます。

// UDP受信
std::array<unsigned char, 128> recv_buf;
asio::ip::udp::endpoint sender_endpoint;

size_t recv_buf_len = socket.receive_from(
    asio::buffer(recv_buf), sender_endpoint);

最後に受信したバイナリデータを文字列に変えていきます。Perlではunpack関数があるのですが、ここでも自作しました。配列を一つずつ見ていきcharの上位と下位をそれぞれ文字列に変換します。

// 受信バイナリデータを文字列に変換
std::string result = "";
for (size_t i = 0; i < recv_buf_len; i++)
{
    result += char2hex((recv_buf[i] >> 4) & 0xf);
    result += char2hex(recv_buf[i] & 0xf);
}

数値を文字にする関数は下記のとおりです。

//0から15までの数値をアスキーに変換
char char2hex(char h)
{
    if (h >= 0 && h <= 9)
        return h + '0';
    else if (h >= 10 && h <= 15)
        return h - 10 + 'A';

    return 0;
}

実行結果は下記の通り。Perlと同じ結果になりました。

send: 500000FFFF03000C001000010400002C0100A80300
rev : D00000FFFF03000800000001000A006400

次回以降、コマンドの構造体を作り、ライブラリで使えるようにしていきたいです。

Qt5とOpenCV4を使ったアプリを作りたい

タイトルにある通り、Qt5とOpenCV4を使ったアプリを作りたいのですが、なかなか思うように進んでいません。両方とも初心者なので入門書から勉強しているところです。ここ2・3ヶ月はOpenCV4基本プログラミングと本で勉強していました。Qt5はYoutubeのチュートリアル動画を使って勉強しています。

一通り勉強してなんとなくわかってきたので、試しにアプリを作ってみました。OpenCVのVideoCaptureで動画を取り込んで、手動でカーソルを合わせるアプリ。顕微鏡にカメラを取り付けて、表示することを想定しています。更新速度が遅かったり、クラッシュしたりと本当に使い物になりませんが、作成するにあたってわかったことを忘備録として書きます。

Qt GUIでは標準ライブラリのスレッドが使えない

ボタンを押して、VideoCaptureを開始し、動画を連続で表示させようとしたら他の操作ができません。スレッドの出番です。標準ライブラリにtreadがあるので、実装して実行したらクラッシュ。わけがわかりませんが、ネットで色々調べると、Qt で使えないとのこと。領域が被る?まだよく理解できてませんが、QThreadを使って実装することができました。

connectには書き方が2種類ある

Qtではsignal、slot、connectでイベントを設定しますが、参考するサイトによって書き方がかなり違うので戸惑ってしまします。下記の2つの書き方があるようです。

①connect(mThread,SIGNAL(NumberChanged(int)),this,SLOT(onNumberChanged(int)));

②connect(mThread, &MyThread::NumberChanged, this, &Dialog::onNumberChanged);

基本は connect(送り元オブジェクト, 送り元の関数, 送り先オブジェクト, 送り先の関数)の関数です。

①はSIGNALやSLOTという命令を使っています。引数は(int)のよう型だけを書くのがポイント。②はクラス名から書き、引数は書きません。

OpenCV4のMatからQt5のQImageへ変換

ネット上に乗っているOpenCVの情報はまだまだ古いものがあり、そのままでは今のバージョンでエラーになることがあります。OpenCVはBGR、QtはRGBの色配列の並びが違うので変換する必要があります。OpenCVで色を変換する場合はcvtColorで色を入れ替えますが、その引数で書かれているCV_BGR2GRAYはCOLOR_BGR2RGBとしなければなりません。QImageに変換するにプログラムは下記の通り。

video >> frame;
        cv::cvtColor(frame,frame,cv::COLOR_BGR2RGB);
        QImage qimgin (frame.data,
                    frame.cols,
                    frame.rows,
                    static_cast<int>(frame.step),
                    QImage::Format_RGB888);

今後

今回のアプリでは動画の更新が1秒間隔になってます。連続にするとクラッシュしてしまうからです。また、カーソルと連続で移動すると同じくクラッシュしてしまします。使い物になりません。まだまだ、QtやOpenCVを使えているというレベルになっていないので、もう少しサンプルを見ながら勉強して出直すつもりです。とりあえずソースはアップしました。

https://github.com/lingmujianshi/CursorGenerator

Qt5 OpenCV4 ライブラリ設定

Qt5 でOpenCV4を使用するときにライブラリの読み込みができなく結構苦労したのでめも書き

 環境
macOS Mojave 10.14
Qt Creator 4.9
Desktop Qt5.12.3 clang 64bit
opencv-4.1.0

pmakeの .proファイルに下記のように記載する

INCLUDEPATH += /usr/local/include/opencv4
QMAKE_LFLAGS +=  -L/usr/local/lib
LIBS += -lopencv_calib3d -lopencv_core -lopencv_dnn -lopencv_features2d -lopencv_flann -lopencv_gapi -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_ml -lopencv_objdetect -lopencv_photo -lopencv_stitching -lopencv_video -lopencv_videoio

参考にサイトでは下記の感じでできるようです。。(参考はUbuntuでやっていたのでmacとは違う?)

LIBS += `pkg-config opencv4 --cflags --libs`

私の環境ではLIBS += -L/usr/local/lib が受け付けてくれませんでした。QMAKE_LFLAGS += -L/usr/local/libというように書くと、ライブラリフォルダを認識するようです。

C++ string型をprintfで出力

C++11以降にあるstring型。すごく便利なのでよく使うのですが、printf出力では使えないと思ってました。ちょっと手を加えて、 string型を c_str() でcの文字列型に変換すればできる。これがわかったときには思わず感動しました。

#include <string>
#include <cstdio>

int main()
{
    std::string str = "Hello world!";
    std::printf("%s\n",str.c_str());

    return 0;
}

OpenCVのサンプルをやるだけで苦労した

将来的にOpenCVを使ってソフトを作るつもりなので、インストールしてサンプルファイルを駆動させようと思ったら、意外と苦労しました。C++でサンプルを動作させるだけなら、何年か前にもやったことがあるのですが、改めてやってみたらできませんでした。

環境

マシン macOS Mojave 10.14.

言語 C++

ライブラリ OpenCV 4.1.0

コンパイラ Clang / LLVM

実行するとどうしても、リンク異常が出てしてしまいます。参考は下記のURLです。

OpenCV/C++で画像処理入門 vol.1 〜画像を表示してみよう〜

ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

ソースからコンパイルして、cmakeを使って実行すると問題なくできました。

C++のOpenCVをCMakeで使う

いろいろ調べた結果、リンカエラーが発生するときは、ライブラリがきちんと割り当てられていなかったということ。pkg-configの設定ファイルが悪かったみたいです。余計なlibファイルも記載されていました。/usr/local/libフォルダからopencvに関連するのファイルを抽出し書き直しましたファイルはこちら。

opencv4.pc

prefix=/usr/local
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib

Name: opencv4
Description: The opencv library
Version: 4.0.0
Cflags: -I${includedir}/opencv4
Libs: -L${libdir} -lopencv_calib3d -lopencv_core -lopencv_dnn -lopencv_features2d -lopencv_flann -lopencv_gapi -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_ml -lopencv_objdetect -lopencv_photo -lopencv_stitching -lopencv_video -lopencv_videoio

pkg-configを実行すると

$ pkg-config opencv4 --cflags --libs
-I/usr/local/include/opencv4 -L/usr/local/lib -lopencv_calib3d -lopencv_core -lopencv_dnn -lopencv_features2d -lopencv_flann -lopencv_gapi -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_ml -lopencv_objdetect -lopencv_photo -lopencv_stitching -lopencv_video -lopencv_videoio

コンパイルと実行は下記のようにやる。ちなみにstd=c++11を指定しないとコンパイルができません。

$ clang++ -std=c++14 $(pkg-config --cflags --libs opencv4) main.cpp
$ ./a.out

AOJ 双方向連結リストで苦しむ

AizuOnlineJudge(AOJ)でアルゴリズムに入りましたが、だんだんと難しくなり、進むペースが遅くなってきました。それでもWeb上のヒントだけで何とか説いてきましたが、ALDS1_3_C でつまずきました。

StackやQueueと同じように配列でプログラムを組んで実行すると、10個目でタイムアウトで引っかかってしまいます。配列に挿入するときや削除するときには1個ずつずらすことをやっていたので、時間がかかりすぎたようです。そのプログラムがこれ。

https://github.com/lingmujianshi/AizuOnlineJudge/blob/master/ALDS1/ALDS1_3_C/ALDS1_3_C_timeout.cpp

他の人が解いた回答を見ましたが全く理解できませんでした。双方向連結リスト?番兵?わからない言葉ばかりです。

そこでとうとうこの本を買ってしまいました。

プログラミングコンテスト攻略のためのアルゴリズムとデータ構造 単行本(ソフトカバー)

本を見るとズバリと回答が書いてありました。双方向連結リストは自分の前の要素と次の要素へそれぞれのポインタを持つ構造体で番兵はリストの先頭を指す特別なノードで設置することで簡略化できるとのこと。

サンプルを丸写しし、投稿したら問題なく通りましたが、実を言うとコードを全然理解できませんでした。Visual Studio Codeのデバッグ機能を使ってみてみると、Node構造体の中身がぜんぜん表示されませんでした。Classを作ると確認する事ができました。サンプルはmallocを使っていたので、せっかくなのでnewを使って書き直しました。そのコードがこちら。

2つ目を追加したときの流れを書いてみたらやっと理解することができました。

    void insert(int key)
    {
        Node *x = new Node();
        x->key = key;
        // 番兵の直後に要素を追加
        x->next = nil->next;
        nil->next->prev = x;
        nil->next = x;
        x->prev = nil;
    }
insert() 図解

https://github.com/lingmujianshi/AizuOnlineJudge/blob/master/ALDS1/ALDS1_3_C/main.cpp#L32-L41