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

表題のままです

Elasticsearchで日本語文章をバラバラの単語に分解して、そいつを [AWS Translate](https://aws.amazon.com/jp/translate/) で翻訳。
翻訳した単語を今度は [AWS Polly](https://aws.amazon.com/jp/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](https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.Translate.TranslateClient.html)

```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](https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.Polly.PollyClient.html)

```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',
]);
```

`VoiceId`は`LanguageCode`で使用可能なものが決まっている模様

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

<img src="https://qiita-image-store.s3.amazonaws.com/0/53200/362acd8e-17e9-42d7-7240-f0a0aa06390d.png" width="50%">

---

Pollyで再生した音声はキャッシュしてる
追加コストなしで再生できるのは良い

> * [Amazon Polly とは? - Amazon Polly](https://docs.aws.amazon.com/ja_jp/polly/latest/dg/what-is.html)