Posted at

適当な日本語文章を形態素解析し、英語に翻訳して音声読み上げさせる

表題のままです

Elasticsearchで日本語文章をバラバラの単語に分解して、そいつを AWS Translate で翻訳。

翻訳した単語を今度は AWS Polly で読み上げさせる(音声ファイル出力)サンプルです。

環境・コードは以下に置いてます

https://github.com/satooon/elasticsearch-test

軽くリポジトリ内の説明を

elasticsearch-test

├── README.md
├── docker
│   └── elasticsearch # Elasticsearchコンテナイメージ定義
│        ├── Dockerfile
│ └── config
├── docker-compose.yml # dockerコンテナ定義
└── php
├── composer.json
├── composer.lock
├── composer.phar
└── test # テストコードディレクトリ

ついでにdocker-compose.ymlも

検証目的のための構成です


docker-compose.yml

version: '3'

services:
elasticsearch:
build: ./docker/elasticsearch
volumes:
- ./docker/elasticsearch/config:/usr/share/elasticsearch/config
ports:
- 9200:9200
expose:
- 9300
environment:
- discovery.type=single-node
ulimits:
nofile:
soft: 65536
hard: 65536
kibana:
image: docker.elastic.co/kibana/kibana:6.4.2
depends_on:
- elasticsearch
ports:
- 5601:5601

コンテナ立てた後はcomposerインストールを実行して、 .env ファイルも作成しておいてください

.env にはご自身のAWSユーザーの access_key と secret_access_key を設定してください。


Elasticsearchのアナライザの設定

日本語の形態素解析にはkuromojiを利用しますが、辞書の更新が止まっているので今回はneologdを使用します。

プラグインは docker/elasticsearch/Dockerfile 内ですでにインストールしてますので、後は設定だけです。

今回は以下のように設定しました。

curl -H 'Content-Type: application/json' -XPUT 'http://localhost:9200/neologd/?pretty' -d'

{
"settings": {
"index":{
"analysis":{
"analyzer" : {
"default" : {
"tokenizer" : "kuromoji_neologd_tokenizer"
}
}
}
}
}
}'

※設定されているかの確認はkibanaで行いました


動作確認

PHPUnitでテストコードを書いてます


php/test/ElasticsearchTest.php

<?php

require dirname(__DIR__) . '/vendor/autoload.php';

class ElasticsearchTest extends PHPUnit\Framework\TestCase
{
public function test_neologdA()
{
$client = new \GuzzleHttp\Client([
'base_uri' => 'http://localhost:9200'
]);
$response = $client->request('GET', '/neologd/_analyze', [
'json' => ['text' => 'こんにちは世界'],
]);

self::assertEquals(200, $response->getStatusCode());
$expected = '{"tokens":[{"token":"こんにちは","start_offset":0,"end_offset":5,"type":"word","position":0},{"token":"世界","start_offset":5,"end_offset":7,"type":"word","position":1}]}';
self::assertJsonStringEqualsJsonString($expected, $response->getBody()->getContents());
}

... 以下略


『こんにちは世界』が 『こんにちは』『世界』 で別々の単語で返ってたらテストOK

で、テスト実行...

$ ./php/vendor/bin/phpunit php/test/ElasticsearchTest.php

PHPUnit 7.4.0 by Sebastian Bergmann and contributors.

.... 4 / 4 (100%)

Time: 84 ms, Memory: 4.00MB

OK (4 tests, 16 assertions)

\(^o^)/トオタ


AWS Translate の確認

ドキュメント: Class Aws\Translate\TranslateClient | AWS SDK for PHP 3.x


php/test/TranslateTest.php

<?php

require dirname(__DIR__) . '/vendor/autoload.php';

class TranslateTest extends PHPUnit\Framework\TestCase
{
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();

(new Dotenv\Dotenv(dirname(__DIR__)))->load();
}

... ...

public function test_translateTextC()
{
$client = new \GuzzleHttp\Client([
'base_uri' => 'http://localhost:9200'
]);
$response = $client->request('GET', '/neologd/_analyze', [
'json' => ['text' => 'こんにちは世界'],
]);
self::assertEquals(200, $response->getStatusCode());
$json = json_decode($response->getBody()->getContents());

$credentials = new Aws\Credentials\Credentials(
getenv('aws_access_key'),
getenv('aws_secret_access_key')
);
$client = new Aws\Translate\TranslateClient([
'region' => 'us-east-1',
'version' => 'latest',
'credentials' => $credentials,
]);

foreach ($json->tokens as $token) {
$result = $client->translateText([
'SourceLanguageCode' => 'ja',
'TargetLanguageCode' => 'en',
'Text' => $token->token,
]);
self::assertNotEmpty($result->get('TranslatedText'));
echo sprintf("\n%s => %s", $token->token, $result->get('TranslatedText'));
}
}
}


↓で対象の言語に翻訳

$result = $client->translateText([

'SourceLanguageCode' => 'ja',
'TargetLanguageCode' => 'en',
'Text' => $token->token,
]);

テストを実行すると

こんにちは => Hello

世界 => World

が表示されるはず


AWS Polly の確認

ドキュメント: Class Aws\Polly\PollyClient | AWS SDK for PHP 3.x


php/test/PollyTest.php

<?php

require dirname(__DIR__) . '/vendor/autoload.php';

class PollyTest extends PHPUnit\Framework\TestCase
{
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();

(new Dotenv\Dotenv(dirname(__DIR__)))->load();
}

... ...

public function test_pollyB()
{
$client = new \GuzzleHttp\Client([
'base_uri' => 'http://localhost:9200'
]);
$response = $client->request('GET', '/neologd/_analyze', [
'json' => ['text' => 'こんにちは世界'],
]);
self::assertEquals(200, $response->getStatusCode());
$json = json_decode($response->getBody()->getContents());

$credentials = new Aws\Credentials\Credentials(
getenv('aws_access_key'),
getenv('aws_secret_access_key')
);
$translateClient = new Aws\Translate\TranslateClient([
'region' => 'us-east-1',
'version' => 'latest',
'credentials' => $credentials,
]);
$pollyClient = new Aws\Polly\PollyClient([
'region' => 'us-east-1',
'version' => 'latest',
'credentials' => $credentials,
]);

foreach ($json->tokens as $token) {
$text = $translateClient->translateText([
'SourceLanguageCode' => 'ja',
'TargetLanguageCode' => 'en',
'Text' => $token->token,
]);
self::assertNotEmpty($text->get('TranslatedText'));

$speech = $pollyClient->synthesizeSpeech([
'LanguageCode' => 'en-US',
'OutputFormat' => 'mp3',
'Text' => $text->get('TranslatedText'),
'TextType' => 'text',
'VoiceId' => 'Salli',
]);

/** @var \GuzzleHttp\Psr7\Stream $stream */
$stream = $speech->get('AudioStream');
self::assertTrue($stream instanceof \GuzzleHttp\Psr7\Stream);
self::assertNotFalse(file_put_contents(sprintf("%s/test_pollyB_%s.mp3", __DIR__, $text->get('TranslatedText')), $stream));
}
}
}


↓で音声に変換

$speech = $pollyClient->synthesizeSpeech([

'LanguageCode' => 'en-US',
'OutputFormat' => 'mp3',
'Text' => $text->get('TranslatedText'),
'TextType' => 'text',
'VoiceId' => 'Salli',
]);

VoiceIdLanguageCodeで使用可能なものが決まっている模様

テストを実行するとphp/test配下にmp3ファイルが出力されるので、再生して確認


Pollyで再生した音声はキャッシュしてる

追加コストなしで再生できるのは良い