AWS
Elasticsearch

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

表題のままです

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で再生した音声はキャッシュしてる
追加コストなしで再生できるのは良い