無料で使用可能な音声合成ソフトをPythonでしゃべらせてみた
目次
はじめに
自己紹介
じげん 求人Div.でエンジニアをしている酒匂と申します。
求人Div.では3つの求人サービスを運用しております。
正社員、派遣、アルバイトなどの幅広い求人情報を扱っており、全国のアルバイト情報を扱っている「アルバイトEX」、正社員求人を専門に扱っている「転職EX」、求人看護師の求人を専門に扱っている「看護師求人EX」があり、これらサービスには複数の企業からいただいた求人情報を掲載させていただいているという特徴があります。
私の業務は主にアルバイトEXの開発・保守を担当しており、転職EX、看護師求人EXの保守も担当しております。
じげん入社前はC言語、VBAを扱える程度でwebについては全く知識が無い状態でしたが、入社してからRailsを学習し始め、現在5年目になります。
本内容ではPythonを使って最近流行っている音声合成ソフトのAPIを叩いて得た知見を共有します。
使用しているPythonのバージョンは3.7.9になります。
音声合成ソフトとは
テキストで指定した言葉を予め録音した音声や、機械で生成した音声で話してくれるソフトのことです。
音声合成ソフトは一般に知られているものでは初音ミク(クリプトン・フューチャー・メディア提供)をはじめとするボーカロイドが有名ではないでしょうか。
また、最近ではCoeFontが提供するおしゃべりひろゆきメーカー(株式会社CoeFont提供)がTwitterなどでバズり、より知名度が上がったかと思います。
今回は、さまざまなAPI化されている音声合成ソフトを使用してその違いやPythonでの扱いを紹介していきます。
Google Cloud Text to Speech
概要
まずはじめに、Googleが提供しているGoogle Cloud Text to Speechについて紹介します。
こちらの特徴としては以下の点が挙げられます。
・40以上の言語に対応している
・月に400万文字の使用まで無料(一部音声は100万文字まで)
利用できる言語が多く、無料枠も他と比べると多いため1番使い勝手が良いと思います。
日本語で話すと明らかに音声合成の声だとわかってしまうのですが、他の言語ではかなり自然な人間の声に聞こえます。
こちらから実際に音声を試すことができます。
https://cloud.google.com/text-to-speech
使い方
ではこちらの使い方を解説していきます。
利用するにはまずGCPへの登録、サービスアカウントの作成〜など工程が必要になりますのでこちらに記載されている手順に沿って進めます。(すべて説明すると長くなりますのでここでは省略します)
https://cloud.google.com/text-to-speech/docs/before-you-begin
次に、Pythonで使用するためにクライアントライブラリを以下のコマンドでインストールします。
pip install --upgrade google-cloud-texttospeech
インストールが完了したら上記の手順で取得したjsonファイルを用いて以下のコードで実行することができます。
import simpleaudio, tempfile, os
from google.cloud import texttospeech
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "○○"
client = texttospeech.TextToSpeechClient()
text = "田中さんこんにちは、今日も良い天気ですね。"
synthesis_input = texttospeech.SynthesisInput(text=text)
#声の設定
voice = texttospeech.VoiceSelectionParams(
name="ja-JP-Wavenet-A",
language_code="ja-JP",
ssml_gender=texttospeech.SsmlVoiceGender.MALE
)
#生成する音声ファイルのエンコード方式
audio_config = texttospeech.AudioConfig(
audio_encoding=texttospeech.AudioEncoding.LINEAR16
)
response = client.synthesize_speech(
input=synthesis_input, voice=voice, audio_config=audio_config
)
#音声ファイルを再生
with tempfile.TemporaryDirectory() as tmp:
with open(f"{tmp}/output.wav", "wb") as f:
f.write(response.audio_content)
wav_obj = simpleaudio.WaveObject.from_wave_file(f"{tmp}/output.wav")
play_obj = wav_obj.play()
play_obj.wait_done()
また、音声合成マークアップ言語のSSMLに対応しているため途中で間をあけたり速度を変更できるなどカスタマイズが可能です。
ssml = "田中さんこんにちは、今日も良い天気ですね。"
synthesis_input = texttospeech.SynthesisInput(ssml=ssml)
VOICEVOX
概要
VOICEVOXはオープンソースの音声合成ソフトで、以下の特徴があります。
・商用・非商用問わず無料で利用が可能
・イントネーションの調整が可能
・日本語のみだが、バリエーション豊富なボイス
・処理はPC内部で行うためある程度スペックが必要
男女共にキャラクターが設定されており、代表的なキャラクターは四国めたん、ずんだもんとなっています。
また一部ですが読み上げる際の感情を選択することができます。
商用でも無料で利用が可能ですのでよくniconicoやYouTubeなどで使用されています。
Windows、Mac、Linuxに対応しており、音声出力する際にCPU・GPUどちらかを使用するため、処理速度を早くするにはある程度のスペックが必要となりますのでご注意ください。
使い方
こちらの使い方ですが、まず公式サイトからソフトをPCにインストールする必要があります。
https://voicevox.hiroshiba.jp/
インストールが完了し、ソフトを立ち上げるとGUI上で操作ができますが、今回はAPIを使用するため立ち上げるだけにしておきます。
ソフトが立ち上がっている間はこちらにアクセスすることでAPIリファレンスを確認することができます。
http://localhost:50021/docs
あとは以下のようなコードで簡単にAPIを使ってしゃべらせることができます。
import requests, simpleaudio, tempfile, json
host = "127.0.0.1" # "localhost"でも可能だが、処理が遅くなる
port = 50021
params = (
("text", "田中さんこんにちは、今日も良い天気ですね。"),
("speaker", 3) # 音声の種類をInt型で指定
)
# 以下の検索結果でどの数値がどういった声なのか記載されている
# https://github.com/VOICEVOX/voicevox_resource/search?q=styleId
response1 = requests.post(
f"http://{host}:{port}/audio_query",
params=params
)
response2 = requests.post(
f"http://{host}:{port}/synthesis",
headers={"Content-Type": "application/json"},
params=params,
data=json.dumps(response1.json())
)
with tempfile.TemporaryDirectory() as tmp:
with open(f"{tmp}/audi.wav", "wb") as f:
f.write(response2.content)
wav_obj = simpleaudio.WaveObject.from_wave_file(f"{tmp}/audi.wav")
play_obj = wav_obj.play()
play_obj.wait_done()
CoeFont
概要
CoeFontは国内産のもので最も高品質な読み上げソフトかと思います。
特徴としては以下の点が挙げられます。
・非常に自然な音声
・自分の声をワンコイン価格でAI音声合成として使用可能
・5,000種類以上の音声が利用可能
・利用に応じて料金プランが設定されている
代表的なキャラクターはアリアル、ミリアル、アベルーニとなっています。
こちらもGoogle Cloud Text to Speechと同様に文字数で料金が発生しますが、アリアル、ミリアルについてはGUI上での音声の使用は無制限で利用できます。
利用するために料金プランを契約する必要があり、無料で使用することもできますが、商用利用するにはBusiness以上のプランが必須となります。
https://coefont.cloud/selectPlan
残念ながら自分が使用していた時はLiteプランでもAPI利用が可能となっていましたが2022年7月1日にプランの改定があり、EnterpriseプランでしかAPI利用できなくなってしまいました。
そのため、ここでは紹介だけとさせていただきます。
公式のAPIリファレンスが用意されていますので、興味のある方はこちらをご覧ください。
https://docs.coefont.cloud/
比較結果
これまで紹介してきた音声合成ソフトを使って、読み方の難しい漢字が使われている歌舞伎の演目「外郎売(ういろううり)」の冒頭を読ませて比較した結果、以下のことがわかりました。
読み上げた部分
拙者親方と申すは、御立会の内に御存知の御方も御座りましょうが、御江戸を発って二十里上方、相州小田原一色町を御過ぎなされて、青物町を上りへ御出でなさるれば、欄干橋虎屋藤右衛門、只今では剃髪致して圓斎と名乗りまする。
元朝より大晦日まで御手に入れまする此の薬は、昔、珍の国の唐人外郎と云う人、我が朝へ来たり。(153文字)
VOICEVOX:ずんだもん
Voiced by https://CoeFont.cloud
1. 処理速度
Google Cloud Text to Speech : 0.7927秒
VOICEVOX : 0.7587秒
CoeFont(GUIで実行) : 体感2秒ほど
処理速度についてはVOICEVOXが一番早かったですが、ここはGPUのスペックにもよるため、安定しているのはGoogle Cloud Text to Speechになるかと思います。
CoeFontについてはGUI上から実行したものの、以前APIで実行した際もこれほど時間がかかっていた記憶があります。
2. 声の自然さ
Google Cloud Text to Speech : かなり棒読み…アナウンサーが読み上げているような抑揚
VOICEVOX : ときどき変なアクセントの付け方をするが、声が優しく人間っぽく聞こえる
CoeFont : 息を吐くような音が入って人間のような話し方で読み上げるが、しゃべり口調で話しているので朗読としては不自然なアクセント
それぞれそのままでの読み上げでは不自然なアクセントや、間を開けるようなことをせずつらつら話すだけになってしまっています。
しかし、調整次第ではもっと自然な話し方に調整することができる余地があるため用途を分けて使用をした方がよいと思います。
3. 読み上げの精度
Google Cloud Text to Speech : 所々違う漢字の読み方をするが、ほとんど正確に読めている
VOICEVOX : ほとんど漢字の読み方ができておらず、正確さに欠けるため辞書登録などが必要
CoeFont(GUIで実行) : VOICEVOXより漢字は読めているがText to Speechまでは正確ではない…
「二十里上方」「欄干橋虎屋藤右衛門」「圓斎」「元朝」など他では読めていない箇所をText to Speechは正確に読むことができていたので驚きました。
応用編
配信で使えるYouTubeライブコメント読み上げとして使ってみた
これまで紹介してきた音声合成サービスをYouTube APIと組み合わせて、配信中のコメントの読み上げとして使えるようにコードを書いてみました。
import requests, simpleaudio, tempfile, time, os, emoji, re, json
from google.cloud import texttospeech
# YouTube API用の設定
YT_API_KEY = "○○"
video_path = "https://www.googleapis.com/youtube/v3/videos"
message_path = "https://www.googleapis.com/youtube/v3/liveChat/messages"
user_list = []
# VOICEVOX用の設定
host = "127.0.0.1"
port = 50021
voicevoxs = {"vox_metan_0": 0, "vox_zunda_1": 1, "vox_metan_2": 2,
"vox_zunda_3": 3, "vox_tumugi_8": 8, "vox_ritsu_9": 9, "vox_hau_10": 10
}
# TextToSpeech用の設定
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "○○"
client = texttospeech.TextToSpeechClient()
# 絵文字を読み上げないよう対策
def remove_emoji(msg):
return "".join(c for c in msg if c not in emoji.UNICODE_EMOJI)
# コメントの整理や処理分けなど
def msg_custom(user, msg):
# 絵文字対策
msg = remove_emoji(msg)
# 草対策
msg_end = msg[-1].lower()
if msg_end in ["w", "w"]:
msg = re.sub("w|w", "わら", msg)
if msg_end == "草":
msg = msg.repleace("草", "くさ")
# リンク対策
if msg[0:4] == "http":
msg = "ゆーあーるえる"
return msg
# NGワードブラックリスト
if re.search("NGワード", msg):
msg = ""
return msg
# コテハンをつける
if msg[0] in ["@", "@"]:
user["name"] = re.sub("@|@", "", msg)
return msg
# コメントで音声変更できるコマンド
if "声変更女1" == msg:
user["voice"] = "ja-JP-Wavenet-A"
elif "声変更女2" == msg:
user["voice"] = "ja-JP-Wavenet-B"
elif "声変更男1" == msg:
user["voice"] = "ja-JP-Wavenet-C"
elif "声変更男2" == msg:
user["voice"] = "ja-JP-Wavenet-D"
elif "声変更男1英語" == msg:
user["voice"] = "en-US-Wavenet-A"
elif "声変更女1英語" == msg:
user["voice"] = "en-US-Wavenet-C"
elif "声変更四国1" == msg:
user["voice"] = "vox_metan_2" # ノーマル
elif "声変更四国2" == msg:
user["voice"] = "vox_metan_0" # あまあま
elif "声変更ずんだ1" == msg:
user["voice"] = "vox_zunda_3" # ノーマル
elif "声変更ずんだ2" == msg:
user["voice"] = "vox_zunda_1" # あまあま
elif "声変更つむぎ" == msg:
user["voice"] = "vox_tumugi_8"
elif "声変更リツ" == msg:
user["voice"] = "vox_ritsu_9"
elif "声変更はう" == msg:
user["voice"] = "vox_hau_10"
return msg
# コメントしたユーザーのリスト作成
def user_create(user_id, user_name):
user = next((inusr for inusr in user_list if inusr["id"] == user_id), '')
# ユーザーリストに無い場合ユーザー情報を作成する
if not bool(user):
user = {"id": user_id, "name": user_name, "voice": voice}
user_list.append(user)
def google_speech(user, msg):
# 名前とコメントをつなげる
ssml = "{}さん{}".format(user["name"], msg)
synthesis_input = texttospeech.SynthesisInput(ssml=ssml)
voice = texttospeech.VoiceSelectionParams(
name=user["voice"],
language_code="ja-JP",
ssml_gender=texttospeech.SsmlVoiceGender.MALE
)
audio_config = texttospeech.AudioConfig(
audio_encoding=texttospeech.AudioEncoding.LINEAR16
)
response = client.synthesize_speech(
input=synthesis_input, voice=voice, audio_config=audio_config
)
# Text To Speech API実行成功
with tempfile.TemporaryDirectory() as tmp:
with open(f"{tmp}/output.wav", "wb") as f:
f.write(response.audio_content)
wav_obj = simpleaudio.WaveObject.from_wave_file(f"{tmp}/output.wav")
play_obj = wav_obj.play()
play_obj.wait_done()
# VOICEVOXで読み上げ
def voicevox_speech(user, msg):
# 名前とコメントをつなげる
msg = "{}さん。{}".format(user["name"], msg)
params = (
("text", msg),
("speaker", voicevoxs[user["voice"]]),
)
response1 = requests.post(
f"http://{host}:{port}/audio_query",
params=params
)
headers = {"Content-Type": "application/json"}
response2 = requests.post(
f"http://{host}:{port}/synthesis",
headers=headers,
params=params,
data=json.dumps(response1.json())
)
with tempfile.TemporaryDirectory() as tmp:
with open(f"{tmp}/audi.wav", "wb") as f:
f.write(response2.content)
wav_obj = simpleaudio.WaveObject.from_wave_file(f"{tmp}/audi.wav")
play_obj = wav_obj.play()
play_obj.wait_done()
# YouTube LiveのチャットIDを取得する
def get_chat_id(yt_url):
video_id = yt_url.replace("https://www.youtube.com/watch?v=", "")
print("video_id : ", video_id)
params = {"key": YT_API_KEY,
"id": video_id,
"part": "liveStreamingDetails"
}
data = requests.get(url, params=params).json()
live_streaming_details = data["items"][0]["liveStreamingDetails"]
if "activeLiveChatId" in live_streaming_details.keys():
chat_id = live_streaming_details["activeLiveChatId"]
print("get_chat_id done!")
else:
chat_id = None
print("NOT live")
return chat_id
# YouTube LiveのチャットIDを元にコメントを取得する
def get_chat(chat_id, page_token):
params = {"key": YT_API_KEY,
"liveChatId": chat_id,
"part": "id,snippet,authorDetails"
}
if type(page_token) == str:
params["pageToken"] = page_token
data = requests.get(url, params=params).json()
# 取得したコメントを読み上げる
def chat_speech(data):
# APIで取得した情報を変数に入れる
for item in data["items"]:
msg = item["snippet"]["displayMessage"]
user_name = item["authorDetails"]["displayName"]
user_id = item["authorDetails"]["channelId"]
voice = "ja-JP-Wavenet-B"
print(msg)
# ユーザーにコテハンやボイス種類の切り替えができるようにリストに保存する(毎回設定してもらう必要あり)
user_create(user_id, user_name)
msg_custom(user, msg) # コメントの内容の整形など
# 先頭の文字でGoogle Cloud Text to SpeechかVOICEVOXか判断して実行する
if user["voice"][0:2] in ["ja", "en"]:
google_speech(user, msg)
elif user["voice"][0:3] == "vox":
voicevox_speech(user, msg)
return data["nextPageToken"]
# 本体部分
def text_to_speech(yt_url):
slp_time = 10 # sec
iter_times = 360 # 回
take_time = slp_time / 60 * iter_times
print("{}分後 終了予定".format(take_time))
print("work on {}".format(yt_url))
chat_id = get_chat_id(yt_url)
next_page_token = None
for ii in range(iter_times):
try:
print("\n")
next_page_token = get_chat(chat_id, next_page_token)
speech_chat(next_page_token)
time.sleep(slp_time)
except:
break
if __name__ == "__main__":
# 起動時に配信のURLを入力
yt_url = input("Input YouTube URL > ")
text_to_speech(yt_url)
コメントを読み上げるだけではなく、よくあるコメント読み上げソフトにあるコメントしたユーザーの名前を登録して話してくれるようにしてみました。
また、読み上げる際に不都合なもの(リンクや絵文字などの読み上げなくてもいいもの)を排除したり、NGワードで登録したものは読み上げないようにしています。
まとめ
所感
これまで比較した結果、声に人間らしさがあまり感じられないがText to Speechが優秀だと改めて感じました。
他の2つに関してはGUI上で文字一つ一つの抑揚を設定することが可能であり、声も豊富であるため、カスタマイズ性についてはVOICEVOX、CoeFontに軍配があがると考えられます。
さいごに
今回使用した合成音声は動画広告の作成などで使えるかと思います。
最近は動画広告に力を入れている企業様も多いので、広告内でしゃべらせる文言の一部(様々な求職者様に引っかかるような職種などのキーワード)だけ変更して大量に似た様な音声を簡単に生成できそうです。
ここまで読んでいただきありがとうございました!
じげんでは様々なプロダクトがあり、挑戦できる環境があります。
ディレクター、エンジニアなど各職種での採用をしておりますので興味がある方はこちらからご応募をお待ちしております!
https://overs.zigexn.co.jp/recruit/