読者です 読者をやめる 読者になる 読者になる

Skype4COM で電話をかけて音声ファイルをながす

こんにちは。休日もプログラミングをしている村瀬です。

先週末、突然 Skype で電話をかけ、相手が出たら指定した音声ファイルを一方的に流して電話を切る、というスクリプトがほしくなったためちょろっと書いてみたのでそれを紹介します。

この記事は Windows + Skype4COM での例ですが、弊社の大塚が自身のブログにLinuxで同じことをやる方法を書いていますので Linux でやりたい方はそちらを参照してみてください。ちなみに、元気が出る電話ではこの大塚の方法を元にしたもの使用していたりします。

さて、Skype4COM ですが、これは SkypeAPI を COM 経由で叩けるようにしてくれるラッパーです。COM は言語を問わず利用することができるので、ここでは perl を使用しました。

ここで紹介するスクリプトの全ソースは github にあげましたので、あわせて参照ください。

https://github.com/typester/skypecall-perl-win32

script/skypecall.pl がシンプルにコマンドラインから動作するスクリプトで、

perl script\skypecall.pl -t [相手のSkypeID or 電話番号] -a [音声Wavファイルのパス]

などのように実行すると指定した相手に電話をかけ、相手が出ると Wav ファイルを再生し、再生し終わると電話を切るということをします。

実装は lib/SkypeCall.pm のなかにかいてあるのですが、API を叩いているところのみ解説します。( $self->skype が COM の Skype オブジェクトだと思ってください )

まず最初に

$self->skype->Attach;

として、このスクリプトとSkypeクライアントを接続します。初回実行時にはAPIアクセスを許可するかどうかのダイアログがでますので許可するようにしてください。

そしておそらくこれは perl の Win32::OLE の制限なのかバグなのかわかりませんが、これを定期的によばないと Skype との接続がきれてしまうようなので、このスクリプトでは1秒ごとにAttachを呼ぶようにしています。

次に電話をかけます。

$self->skype->PlaceCall( $self->target );

これで $self->target で指定した SkypeID もしくは電話番号に電話をかけることができます。電話番号の場合は +81 などと国番号からの番号を指定する必要があります。

電話をかけたら CallStatus イベントというイベントを監視し、相手が電話に出るのを待ちます。

sub skype_callstatus_handler { ... }

という部分がそれです。

ここにわたってくる $status が状態を表していますのでそこで振り分けていて、相手が電話に出たら

$self->skype->SendCommand(
    $self->skype->Command(1, 'ALTER CALL ' . $call->Id . qq{ SET_INPUT FILE="$self->{audio_file}"})
);

と、ALTER CALL コマンドを実行して会話の入力を指定したwavファイルに切り替えます。これで、自動的にこのwavファイルが相手に対して再生されます。

あとは再生が終わったら接続を切ればいいのですが、再生が終わったということを検出するイベントがよくわからなかったため、あらかじめ Audio::Wav を使用して Wav ファイルの秒数を取得しておいて、その秒数がたったら接続を切るという処理にしています。

$kernel->delay( finish => $self->audio_length + $self->margin, $call );

という部分がそれですね。

コードの主要部分の説明は以上ですが、今回はコマンドラインからではなくWebアプリからキックして使うような使い方がしたかったため、TCP経由でこのコマンドが叩ける簡単なデーモンも作成しました。

それが script/skypecalld.pl というもので、

perl script\skypecalld.pl -p [待ち受けポート番号]

などのように起動します。

このデーモンを使用して電話をかけるには

use strict;
use warnings;

use POE::Component::IKC::ClientLite;

my $poe = create_ikc_client( ip => 'skypecalldのアドレス', port => skypecalldのポート番号 )
    or die POE::Component::IKC::ClientLite::error();

$poe->post( 'ikc_server/execute_call', { target => '相手のSkypeID', audio_file => '音声ファイルのバス' });

などとすればOKです。

これでやりたいことが実現できました。perl はモジュールがそろっていてやりたいことがすぐできて素敵ですね!

あ、書き忘れましたが Skype 側の制限でwavファイルは 16 KHz mono, 16 bit な無圧縮PCMファイルしか再生できませんのでご注意を。

弊社では Skype のみならず VoIP 全般や P2P 技術に詳しい技術者を募集しています!