Microsoft Translator APIを使ってLINEから翻訳させてみた - Qiita
Microsoft Translator APIを使ってLINEから翻訳させてみた - Qiita... |
構想
LINEから日本語でボイスメッセージを受信し、翻訳された文字列をLINEにリプライするという言葉でいうと単純なもの。
方法としては二つあると考えた。
- ボイスメッセージを文字に変換し、前回利用したMicrosoft Translate API経由で翻訳する
- ボイスメッセージを直接翻訳できるAPI経由で翻訳する
少し調べた結果、今回は上の一旦文字に変換して翻訳することにした。
理由としては音声から文字への変換レベルを検証したことが大きな理由だ。
利用API
翻訳APIはTranslate APIを利用した。
音声から文字列への変換はいろいろと調べた結果、Microsoft Bing Speech APIを選択した。
候補としては他に
あたりを探った。音声は日本語と言う事でdocomo 音声認識APIが第一候補であったが利用期間やプラットフォームの制限で諦めた。
Microsoftのは5,000/月まで無料利用ができるというのも大きかった。
環境
- AWS EC2
- PHP5.6
- LINEBot SDK
LINEBotでボイスメッセージを受信
LINEBot Messaging APIの記事は数あれど、なかなかボイスメッセージの記事はなかった。
しかしLINEがSDKでexampleを提供してくれていたのでそれを見ながら試してみた。
LINEBot SDK導入
$composer require linecorp/line-bot-sdk
ボイスメッセージ受信
LINEからボイスメッセージを受信する部分を切り出してみた。
注意してほしいのはBodyで取得できる音声形式はm4aということだ。これで少しはまった。
<?php
define("LINE_MESSAGING_API_CHANNEL_SECRET", '<YOUR_SECRET>');
define("LINE_MESSAGING_API_CHANNEL_TOKEN", '<YOUR_ACCESS_TOKEN>');
require __DIR__."/vendor/autoload.php";
$bot = new \LINE\LINEBot(
new \LINE\LINEBot\HTTPClient\CurlHTTPClient(LINE_MESSAGING_API_CHANNEL_TOKEN),
['channelSecret' => LINE_MESSAGING_API_CHANNEL_SECRET]
);
$signature = $_SERVER["HTTP_".\LINE\LINEBot\Constant\HTTPHeader::LINE_SIGNATURE];
$body = file_get_contents("php://input");
$events = $bot->parseEventRequest($body, $signature);
foreach ($events as $event) {
if ($event instanceof \LINE\LINEBot\Event\MessageEvent\AudioMessage) {
$contentId = $event->getMessageId();
$audio = $bot->getMessageContent($contentId)->getRawBody();
$tmpfilePath = tempnam('/tmp', 'audio-');
unlink($tmpfilePath);
$filePath = $tmpfilePath . '.m4a';
$filename = basename($filePath);
$fh = fopen($filePath, 'x');
fwrite($fh, $audio);
fclose($fh);
(以下略)
Speech To Text
tmpに保存した音声ファイルをBing Speech APIに投げてテキスト変換を行う。
APIに投げる際の音声ファイル形式はwavということに注意してほしい。
今回はEC2にインストールしたffmpegコマンドでm4aからwavファイルに変換した。
API仕様はここにあるので参考に。
STTのソースはきれいではないがこちらも。
流れとしては
- m4aからwavに変換
- Bing Speech APIのAccess Token取得
- APIで変換
exec("ffmpeg -i /tmp/".$filename." -vn -acodec pcm_s16le -ac 2 -ar 44100 -f wav /tmp/".basename( $filename, ".m4a").".wav");
$data = "/tmp/".basename( $filename, ".m4a").".wav";
$text = speechToText($data, 'ja-JP');
(中略)
function speechToText($filepath, $lang) {
$access_token = getSpeechAccessToken();
$sttServiceUri = "https://speech.platform.bing.com/recognize/query";
$sttServiceUri .= @"?scenarios=ulm"; // websearch is the other main option.
$sttServiceUri .= @"&appid=D4D52672-91D7-4C74-8AD8-42B1D98141A5"; // You must use this ID.
$sttServiceUri .= @"&locale=".$lang; // We support several other languages. Refer to README file.
$sttServiceUri .= @"&device.os=wp7";
$sttServiceUri .= @"&version=3.0";
$sttServiceUri .= @"&format=json";
$sttServiceUri .= @"&instanceid=565D69FF-E928-4B7E-87DA-9A750B96D9E3";
$sttServiceUri .= @"&requestid=".guid();
$ctx = stream_context_create(array(
'http' => array(
'timeout' => 1
)
)
);
$data = file_get_contents($filepath, 0, $ctx); //your wave file path here
$options = array(
'http' => array(
'header' => "Content-type: audio/wav; samplerate=8000\r\n" .
"Authorization: "."Bearer ".$access_token."\r\n",
'method' => 'POST',
'content' => $data,
),
);
$context = stream_context_create($options);
// get the response result
$result = file_get_contents($sttServiceUri, false, $context);
if (!$result) {
return "tranlate missed!";
}
else{
$arr = json_decode($result);
return $arr->results[0]->lexical;
}
}
function guid(){
if (function_exists('com_create_guid')){
return com_create_guid();
}else{
mt_srand((double)microtime()*10000);
$charid = strtoupper(md5(uniqid(rand(), true)));
$hyphen = chr(45);// "-"
$uuid = substr($charid, 0, 8).$hyphen
.substr($charid, 8, 4).$hyphen
.substr($charid,12, 4).$hyphen
.substr($charid,16, 4).$hyphen
.substr($charid,20,12);
return $uuid;
}
}
// Bing Speech API用のAccessToken
function getSpeechAccessToken() {
$AccessTokenUri = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken";
$apiKey = "<YOUR_KEY>";
$ttsHost = "https://speech.platform.bing.com";
$options = array(
'http' => array(
'header' => "Ocp-Apim-Subscription-Key: ".$apiKey."\r\n" .
"content-length: 0\r\n",
'method' => 'POST',
),
);
$context = stream_context_create($options);
//get the Access Token
return file_get_contents($AccessTokenUri, false, $context);
}
翻訳
今回はバリニーズと話すことを目的としているので(なぜ?)インドネシア語に変換してみた。
前回書いたソースと同じ。
$reply_token = $event->getReplyToken();
$bot->replyText($reply_token, translation($text));
}
else if($event instanceof \LINE\LINEBot\Event\MessageEvent\TextMessage) {
$reply_token = $event->getReplyToken();
$bot->replyText($reply_token, translation($event->getText()));
}
else {
$reply_token = $event->getReplyToken();
$bot->replyText($reply_token, 'ありがとう! "Terima Kashi!"');
}
}
function translation($text) {
$client_id = "<YOUR_ID>";
$client_secret = "<YOUR_SECRET>";
$access_token = getTranslateAccessToken($client_id, $client_secret)->access_token;
$text = mb_convert_kana($text, 'KVas');
$from = '';
$to = '';
if (preg_match('/[ぁ-んァ-ヶー一-龠、。]/u',$text)) {
$from = 'ja'; //日本語
$to = 'id'; //インドネシア語
}
else{
$from = 'id';
$to = 'ja';
}
return tranlator($access_token, array(
'text' => $text,
'to' => $to,
'from' => $from));
}
// 翻訳実行
function tranlator($access_token, $params){
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => "https://api.microsofttranslator.com/v2/Http.svc/Translate?".http_build_query($params),
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => array(
"Authorization: Bearer ". $access_token),
));
preg_match('/>(.+?)<\/string>/',curl_exec($ch), $m);
return $m[1];
}
// Microsoft Translate API用のAccessToken
function getTranslateAccessToken($client_id, $client_secret, $grant_type = "client_credentials", $scope = "http://api.microsofttranslator.com"){
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13/",
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(array(
"grant_type" => $grant_type,
"scope" => $scope,
"client_id" => $client_id,
"client_secret" => $client_secret
))
));
return json_decode(curl_exec($ch));
}
結果
結果、見事に思うような結果が返ってきた。
これでバリに行っても安心である。
友達とBotとグループを組めばリアルタイム翻訳ちっくなこともできる。若干見づらいかもしれないけれど
も。
フローチャート
全体ソース
<?php
define("LINE_MESSAGING_API_CHANNEL_SECRET", '<YOUR_SECREt>');
define("LINE_MESSAGING_API_CHANNEL_TOKEN", '<YOUR_TOKEN>');
require __DIR__."/vendor/autoload.php";
$bot = new \LINE\LINEBot(
new \LINE\LINEBot\HTTPClient\CurlHTTPClient(LINE_MESSAGING_API_CHANNEL_TOKEN),
['channelSecret' => LINE_MESSAGING_API_CHANNEL_SECRET]
);
$signature = $_SERVER["HTTP_".\LINE\LINEBot\Constant\HTTPHeader::LINE_SIGNATURE];
$body = file_get_contents("php://input");
$events = $bot->parseEventRequest($body, $signature);
foreach ($events as $event) {
if ($event instanceof \LINE\LINEBot\Event\MessageEvent\AudioMessage) {
$contentId = $event->getMessageId();
$audio = $bot->getMessageContent($contentId)->getRawBody();
$tmpfilePath = tempnam('/tmp', 'audio-');
unlink($tmpfilePath);
$filePath = $tmpfilePath . '.m4a';
$filename = basename($filePath);
$fh = fopen($filePath, 'x');
fwrite($fh, $audio);
fclose($fh);
//$data = '/tmp/'.$filename;
exec("ffmpeg -i /tmp/".$filename." -vn -acodec pcm_s16le -ac 2 -ar 44100 -f wav /tmp/".basename( $filename, ".m4a").".wav");
$data = "/tmp/".basename( $filename, ".m4a").".wav";
$text = speechToText($data, 'ja-JP');
$reply_token = $event->getReplyToken();
$bot->replyText($reply_token, translation($text));
}
else if($event instanceof \LINE\LINEBot\Event\MessageEvent\TextMessage) {
$reply_token = $event->getReplyToken();
$bot->replyText($reply_token, translation($event->getText()));
}
else {
$reply_token = $event->getReplyToken();
$bot->replyText($reply_token, 'ありがとう! "Terima Kashi!"');
}
}
// Bing Speech API用のAccessToken
function getSpeechAccessToken() {
$AccessTokenUri = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken";
$apiKey = "<YOUR_KEY>";
$ttsHost = "https://speech.platform.bing.com";
$options = array(
'http' => array(
'header' => "Ocp-Apim-Subscription-Key: ".$apiKey."\r\n" .
"content-length: 0\r\n",
'method' => 'POST',
),
);
$context = stream_context_create($options);
//get the Access Token
return file_get_contents($AccessTokenUri, false, $context);
}
// Microsoft Translate API用のAccessToken
function getTranslateAccessToken($client_id, $client_secret, $grant_type = "client_credentials", $scope = "http://api.microsofttranslator.com"){
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13/",
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(array(
"grant_type" => $grant_type,
"scope" => $scope,
"client_id" => $client_id,
"client_secret" => $client_secret
))
));
return json_decode(curl_exec($ch));
}
function speechToText($filepath, $lang) {
$access_token = getSpeechAccessToken();
$sttServiceUri = "https://speech.platform.bing.com/recognize/query";
$sttServiceUri .= @"?scenarios=ulm"; // websearch is the other main option.
$sttServiceUri .= @"&appid=D4D52672-91D7-4C74-8AD8-42B1D98141A5"; // You must use this ID.
$sttServiceUri .= @"&locale=".$lang; // We support several other languages. Refer to README file.
$sttServiceUri .= @"&device.os=wp7";
$sttServiceUri .= @"&version=3.0";
$sttServiceUri .= @"&format=json";
$sttServiceUri .= @"&instanceid=565D69FF-E928-4B7E-87DA-9A750B96D9E3";
$sttServiceUri .= @"&requestid=".guid();
$ctx = stream_context_create(array(
'http' => array(
'timeout' => 1
)
)
);
$data = file_get_contents($filepath, 0, $ctx); //your wave file path here
$options = array(
'http' => array(
'header' => "Content-type: audio/wav; samplerate=8000\r\n" .
"Authorization: "."Bearer ".$access_token."\r\n",
'method' => 'POST',
'content' => $data,
),
);
$context = stream_context_create($options);
// get the response result
$result = file_get_contents($sttServiceUri, false, $context);
if (!$result) {
return "tranlate missed!";
}
else{
$arr = json_decode($result);
return $arr->results[0]->lexical;
}
}
function guid(){
if (function_exists('com_create_guid')){
return com_create_guid();
}else{
mt_srand((double)microtime()*10000);
$charid = strtoupper(md5(uniqid(rand(), true)));
$hyphen = chr(45);// "-"
$uuid = substr($charid, 0, 8).$hyphen
.substr($charid, 8, 4).$hyphen
.substr($charid,12, 4).$hyphen
.substr($charid,16, 4).$hyphen
.substr($charid,20,12);
return $uuid;
}
}
function translation($text) {
$client_id = "<YOUR_ID>";
$client_secret = "<YOUR_SECRET>";
$access_token = getTranslateAccessToken($client_id, $client_secret)->access_token;
$text = mb_convert_kana($text, 'KVas');
$from = '';
$to = '';
if (preg_match('/[ぁ-んァ-ヶー一-龠、。]/u',$text)) {
$from = 'ja';
$to = 'id';
}
else{
$from = 'id';
$to = 'ja';
}
return tranlator($access_token, array(
'text' => $text,
'to' => $to,
'from' => $from));
}
// 翻訳実行
function tranlator($access_token, $params){
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => "https://api.microsofttranslator.com/v2/Http.svc/Translate?".http_build_query($params),
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => array(
"Authorization: Bearer ". $access_token),
));
preg_match('/>(.+?)<\/string>/',curl_exec($ch), $m);
return $m[1];
}
事前にQiitaで晒して大丈夫かわからないが、LINE BOT AWARDS にこれで応募してみた。