Polly + Lambda + S3 でテキストを自動で音声化!

エンジニアの山田です。 東京オフィスのHPがリニューアルされたということで、心機一転頑張りたいと思います! 前…

エンジニアの山田です。
東京オフィスのHPがリニューアルされたということで、心機一転頑張りたいと思います!

前回、AWSのサーバレス構成を紹介したのですが、自分自身 Lambda を触る機会がほとんどなく、
これはいかんなあと思っていたので、千里の道も一歩から、練習用に簡単なサンプルを作ってみました。

 

概要

タイトルにある通り、テキストファイルをアップロードしたら、その内容を自動で音声に変換してくれるようなサンプルを作ってみたいと思います。

ということで今回使うものはこちら

  • Polly => テキストを音声化してくれるサービス
  • Lambda => AWS上で関数を実行できるサービス
  • S3 => ストレージサービス

Lambda もさることながら、Polly もすごいんです。
わりと最近追加されたサービスなのですが、今の機械音声ってこんなに滑らかなんだ・・・と驚かされました。
あまり Lambda に興味がない方も最後に Polly で生成した音声を載せるのでぜひ聴いてみてください!

完成イメージはこんな感じです。

  1. S3 にテキストファイルをアップロード
  2. Lambda がアップロードをトリガーに起動、S3 にアップロードされたテキストを Polly に投げる
  3. Polly が渡されたテキストを元に音声ファイルを生成する
  4. Lambda が生成された音声ファイルを S3 にアップロード
  5. 再度 S3 を見に行くと音声ファイルができているはず!

 

作ってみる

全体像も見えたところで早速作っていきましょう。

1. S3 の準備

まずは S3 に適当なバケットを作ります。
今回のサンプルでは、わかりやすいようにテキスト用のフォルダと、音声用のフォルダをバケット内に用意しておきます。

2. IAM ロールの作成

IAM から Lambda で使うためのロールを作りましょう。
AWS サービスロールの中から 「AWS Lambda」 を選びます。

以下の権限を付与しておきます。

  • S3FullAccess
  • PollyFullAccess

3. Lambda Function の作成

では、今回のメインとなる Lambda Function を書いていきます。
Lambda では、Python と JavaScript、 Java が使えるので、今回は Java を使ってみます。
Java を使う場合は Eclipse に AWS のプラグインを入れると、お手軽にデプロイまでできてしまうのでオススメです。
久しぶりの Java、ほぼ初めての Lambda ということで四苦八苦しましたが、最終的に以下のような感じになりました。

package jp.co.sample;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;

import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.polly.AmazonPolly;
import com.amazonaws.services.polly.AmazonPollyClientBuilder;
import com.amazonaws.services.polly.model.SynthesizeSpeechRequest;
import com.amazonaws.services.polly.model.SynthesizeSpeechResult;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.event.S3EventNotification.S3EventNotificationRecord;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.util.IOUtils;

public class LambdaFunctionHandler implements RequestHandler<S3Event, Object> {

	// 保存先の情報(作成したバケット名、フォルダを指定してください。)
	private static final String DEST_BUCKET_NAME = "2017-test-bucket";
	private static final String AUDIO_FILE_PREFIX = "mp3/";
	
	private AmazonS3 s3 = AmazonS3ClientBuilder.defaultClient();

    @Override
    public Object handleRequest(S3Event input, Context context) {
    	LambdaLogger lambdaLogger = context.getLogger();
        S3EventNotificationRecord record = input.getRecords().get(0);
        String bucketName = record.getS3().getBucket().getName();
        String fileName = record.getS3().getObject().getKey();
        
        // とりあえずprefixを取り除いて拡張子をmp3に
        String convertedFileName = fileName.replaceAll(".*/", "").replaceAll(".txt", ".mp3");
        
        try {
        	// S3 からファイルの内容を取得
        	String content = getContentFromS3(bucketName, fileName);
			
			// 取得した内容を Polly で音源化
			InputStream stream = pollyRequest(content);
	        	
			// 音源を S3 のバケットへ保存
	        PutObjectResult result = putS3(convertedFileName, stream);

	        return result;
		} catch (IOException e) {
			lambdaLogger.log(e.getMessage());
		}
        return null;
    }
    
    /**
     * 指定されたバケット名、ファイル名から内容を文字列として取得
     */
    private String getContentFromS3(String bucketName, String fileName) throws IOException {
    	// オブジェクトの取得
    	GetObjectRequest request = new GetObjectRequest(bucketName, fileName);
        S3Object s3Object = s3.getObject(request);     

        // 文字列に変換
    	StringBuilder sb = new StringBuilder();
        BufferedReader reader = new BufferedReader(new InputStreamReader(s3Object.getObjectContent()));
    	String line;
		while((line = reader.readLine()) != null) {
			sb.append(line);
		}
		
		return sb.toString();
    }
    
    /**
     * 指定されたテキストを Polly へリクエストして音声化
     */
    private InputStream pollyRequest(String text) {  
    	// リクエストの作成(今回は英語の中でも可愛かった Ivy で。別の言語の場合は各言語のVoiceIDの中から選んでセット)
        SynthesizeSpeechRequest request = new SynthesizeSpeechRequest();
        request.setVoiceId("Ivy");
        request.setOutputFormat("mp3");
        request.setText(text);
        request.setTextType("text");
        
        // Polly を使って音声化
        AmazonPolly polly = AmazonPollyClientBuilder.standard().withRegion(Regions.US_EAST_1).build();
        SynthesizeSpeechResult result = polly.synthesizeSpeech(request);
        return result.getAudioStream();
    }
    
    /**
     *  指定されたキーでストリームをS3に保存
     */
    private PutObjectResult putS3(String key, InputStream stream) throws IOException {
        // メタデータの作成
    	byte[] bytes = IOUtils.toByteArray(stream);
    	ObjectMetadata metadata = new ObjectMetadata();
    	metadata.setContentLength(bytes.length);
    	
    	// S3 へ保存(mp3 以下に保存されるようプレフィックスを付けておく)
    	ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        PutObjectRequest request = new PutObjectRequest(DEST_BUCKET_NAME, AUDIO_FILE_PREFIX + key, byteArrayInputStream, metadata);
        return s3.putObject(request);
    }
}

 

4. Lambda Function の設定

ここまできたらあと一息です。
S3へのアップロードのタイミングでLambdaが動作するように設定しましょう。

Lambda の関数一覧から、今回作った関数を選択します。

関数の設定画面に遷移するので、「トリガー」 タブから 「トリガーを追加」 を選択します。

S3 を選択して、以下のように設定します。

これで完成です!

 

動かしてみる

それでは実際に動かしてみましょう!

Pollyの公式ページの英文を使って試してみます。

Amazon Polly is a service that turns text into lifelike speech. Polly lets you create applications that talk, enabling you to build entirely new categories of speech-enabled products. Polly is an Amazon AI service that uses advanced deep learning technologies to synthesize speech that sounds like a human voice. Polly includes 47 lifelike voices spread across 24 languages, so you can select the ideal voice and build speech-enabled applications that work in many different countries.

引用元: https://aws.amazon.com/polly/?nc1=h_ls

まずはこの英文を適当なテキストファイルにしてS3にアップロードします。
手順1で作成したバケットのテキスト用のフォルダに配置しましょう。

音声用のフォルダに移動してみると・・・

おわかりいただけたでしょうか?
同じ名前の音声ファイルができてますね!

 
早速聴いてみましょう!

いや機械音声とは思えないですね!
発音はカスタマイズできるようですが、デフォルトでもこれだから驚きです。
アップロードしたテキストの内容をしっかり読み上げてくれているのがわかるかと思います。

 

まとめ

今回は手動でアップロードしましたが、ちょっと手を加えて自動化すれば、
寝ている間に大量のテキストを音声化したりもできてしまいます。

時間のかかる処理であれば、SNSと連携して、終わったら通知を送る、なんてのもありかもしれませんね。

こんな具合にAWSは組み合わせるだけで簡単に色々なことができてしまうので面白いです!
無料枠内だけでも今回のように十分遊べるので、ぜひ触ってみてください!