様々な企業がテキストから読み上げ音声を生成するAPIを公開していますが、その中でもGCPのText-to-Speech APIは日本語読み上げのクオリティが高かった為、実装をしてみました。
↓Text-to-Speech APIは過去記事でラズパイ(Python)でのテキスト読み上げの時にも使っています。今回は.NET Coreからの利用方法になります。
- Text-to-Speech APIとは
- やりたいこと
- 実行環境
- Text-to-Speech APIの準備
- .NET Core環境の構築
- NuGetパッケージの追加
- サービスアカウントの認証情報をロード
- APIを呼ぶクラスを作成
- Program.csからAPIを呼ぶ
- デバッグする
- ハマったところ
Text-to-Speech APIとは
GCPが提供するテキスト読み上げAPIです。日本語テキストを付与してリクエストすると、そのテキストを読み上げた音声ファイルをレスポンスしてくれます。
ただテキスト読み上げするのではなく、文脈に合わせて抑揚を付けてくれたり、読み仮名もそれなりに解釈してくれるのでなかなか高品質です。 声の性別、高さ、速さを指定して、ある程度好みの声のトーンに近づけることも可能です。
日本語のVoice typeには"Basic", "WaveNet"があり、"WaveNet"は料金は少し高くなりますが、より人間の発声に近くなります。
↓のページから音声サンプルが聞けます。
AWSでも似たサービスで「Amazon Polly」がありますが、こちらは日本語を喋らせると若干カタコト感が否めません。
ただこちらはAWSの各サービスと連携しやすいので、品質的に許容できるなら採用しても良いかもしれません。
↓のページから音声サンプルが聞けます。
やりたいこと
実行環境
$ docker --version Docker version 20.10.10, build b485636 $ docker-compose --version Docker Compose version v2.1.1
Text-to-Speech APIの準備
GCPではまず使いたいAPIを有効化する必要があります。 「APIとサービス」から「APIとサービスの有効化」を押します。
APIの検索ボックスが表示されますので、Cloud Text-to-Speech APIを探します。
見つけられたら、「有効にする」を押して有効化します。
次に外部からAPIへリクエストする為のサービスアカウントをGCPで作成します。
「IAMと管理」-「サービスアカウント」から「サービスアカウントを作成」を押し、新しくサービスアカウントを作成します。アカウント名は識別しやすい名前にしておきます。
作成したサービスアカウントの詳細画面の「キー」の一覧から「鍵を追加」を押します。
作成するキーのタイプはJSONを選び作成すると、自動的にブラウザからjsonがダウンロードされます。このjsonがAPIのリクエスト時に必要な認証情報になります。とりあえずこの時点ではファイル名をgcp-service-account.json
に変更だけしておきます。
サービスアカウントを作成すると認証情報が記載されたjsonがダウンロードされますが、後で使うので一時保存しておきます。
.NET Core環境の構築
ここからは具体的なソースコード記述に入っていきます。
今回.NET Core環境はDockerで構築します。諸事情でDockerが使えない場合はローカルにインストールした.NET Core SDKから構築してもOKです。
Dockefile
FROM mcr.microsoft.com/dotnet/sdk:5.0 WORKDIR /project EXPOSE 5000
docker-compose.yml
version: '3' services: dotnet: build: . ports: - 5000:5000 volumes: - .:/TextToSpeechTest tty: true
Dockerfile, docker-compose.ymlが作成できたら、コンテナを立ち上げてdotnet CLIからCUIアプリを新規作成します。
# Dockerコンテナの立ち上げ $ docker-compose up -d # コンテナに入る $ docker-compose exec dotnet bash # コンテナからCUIアプリを新規作成 root@*******:/project# dotnet new console Getting ready... The template "Console Application" was created successfully. Processing post-creation actions... Running 'dotnet restore' on /project/project.csproj... Determining projects to restore... Restored /project/project.csproj (in 57 ms). Restore succeeded.
dotnet new console
実行後、Program.cs
, TextToSpeechTest.csproj
と付随する各種ファイルが自動生成されるはずです。
NuGetパッケージの追加
Text-to-Speech APIの利用の為にはNuGetパッケージGoogle.Cloud.TextToSpeech.V1
が必要ですので、パッケージをインストールをファイルに定義し、インストールします。
パッケージの追加には色々な方法がありますが、ここではNuGetパッケージを定義するファイルであるcsprojに追記してrestoreする方法を書きます。
TextToSpeechTest.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> // ↓を追記 <PackageReference Include="Google.Cloud.TextToSpeech.V1" Version="2.4.0"/> </ItemGroup> </Project>
# csprojの変更を反映させる
root@*******:/project# dotnet restore
サービスアカウントの認証情報をロード
前のステップで一時保管していたgcp-service-account.json
の場所を移動させ、プロジェクトのルートパスから見て./credentials/gcp/gcp-service-account.json
に配置します。(このパスはあくまで当記事で指定しているだけですので、任意で変えてもOKですが、以下のソースコードのcredentialPath
も合わせて修正ください。)
Program.cs
と同階層にGCPCredentialManager.cs
を作成し、jsonのパスを環境変数で指定します。これでAPIへリクエストする際にjsonを利用して認証が行われるようになります。
GCPCredentialManager.cs
using System; using System.IO; public static class GCPCredentialManager { private static readonly string credentialPath = Path.Combine(Environment.CurrentDirectory, @"credentials/gcp/gcp-service-account.json"); /// <summary>GCPへアクセスするための認証情報設定をする。</summary> public static bool SetCredentials() { // GCPのNugetパッケージは環境変数"GOOGLE_APPLICATION_CREDENTIALS"から認証情報を参照しようとするので、ここでパスを指定する Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", credentialPath); return true; } }
APIを呼ぶクラスを作成
TextSpeechAPI
クラスにはメインロジックであるText-to-Speech APIを呼び出す処理を記述します。
処理が完了すると./output
にoutput.mp3
という音声ファイルが出力されるようになっています。
読み上げる声については細かくカスタマイズできますので、いろいろパラメータをいじってみると楽しいです😊
Google.Cloud.TextToSpeech.V1
のリファレンスは下記からどうぞ。
TextSpeechAPI.cs
using Google.Cloud.TextToSpeech.V1; using System; using System.IO; public static class TextToSpeechAPI { private static readonly string localBasePath = Path.Combine(Environment.CurrentDirectory, @"output"); /// <summary>テキストから音声データを取得する</summary> public static string GetSpeechSound(string text, string languageCode = "ja-JP", SsmlVoiceGender ssmlVoiceGender = SsmlVoiceGender.Female) { // 読み上げるテキストをSynthesisInputで定義。 SynthesisInput input = new SynthesisInput { Text = text }; // 声の各種設定 VoiceSelectionParams voiceSelection = new VoiceSelectionParams { // 言語コード LanguageCode = languageCode, // 使用する声の種類 Name = "ja-JP-Wavenet-A", // 声の性別 SsmlGender = ssmlVoiceGender, }; var audioConfig = new AudioConfig { // エンコーディング方法 AudioEncoding = AudioEncoding.Mp3, // 音量の増加値 VolumeGainDb = 5, // 声のピッチ Pitch = 1.0, // 読み上げ速度 SpeakingRate = 1.0, }; // サービスアカウントの認証情報をロードする GCPCredentialManager.SetCredentials(); // Text-To-Speech APIから音声ファイル取得 TextToSpeechClient client = TextToSpeechClient.Create(); SynthesizeSpeechResponse response = client.SynthesizeSpeech(input, voiceSelection, audioConfig); return WriteLocalFile(Path.Combine(localBasePath, "output.mp3"), response); } private static string WriteLocalFile(string writePath, SynthesizeSpeechResponse response) { // mp3ファイルへ書き込み if (!Directory.Exists(localBasePath)) { Directory.CreateDirectory(localBasePath); } using (FileStream stream = File.Create(writePath)) { response.AudioContent.WriteTo(stream); } return writePath; } }
Program.csからAPIを呼ぶ
Program.cs
には、アプリ実行時のトップレベルの処理を記述します。
ここでは実際に読み上げる文字列を定義し、TextToSpeechAPI
クラスのメソッドを叩くだけです。
Program.cs
using System; namespace TextToSpeechTest { class Program { static void Main(string[] args) { Console.WriteLine("now generating..."); // 読み上げるテキスト string text = "あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。"; TextToSpeechAPI.GetSpeechSound(text); Console.WriteLine("output completed!"); } } }
デバッグする
準備ができましたのでdotnet run
をコンソールに入力してアプリを実行してみましょう。
# Dockerコンテナの立ち上げ $ docker-compose up -d # コンテナに入る $ docker-compose exec dotnet bash # コンテナからCUIアプリを新規作成 root@*******:/project# dotnet run
うまく動作すれば./output/output.mp3
が生成されているはずです。
あとは音声が気にくわなければ各種パラメータを変えて微調整するだけです。
ハマったところ
サービスアカウントの認証情報をロードするためには、環境変数にjsonのパスをセットしないといけなかったのですが、この方法に辿り着くまで結構時間がかかりました。
今回は単純にローカルでデバッグするのでファイルをそのままプロジェクトのディレクトリに配置しましたが、AWS Lambda上で実行させたい場合はS3からjson取得処理を挟むといった工夫が必要になります。