LoginSignup
6

More than 1 year has passed since last update.

posted at

updated at

Organization

ElixirでText to Speech REST API(Azure, ニューラル音声)を使ってみて、フレンドリーなヴォイスに驚く

はじめに

前提

Text to Speech

ドキュメント

Text to Speechが使えるようにする

スクリーンショット 2021-03-20 7.07.57.png

料金

  • 料金は、https://azure.microsoft.com/ja-jp/pricing/details/cognitive-services/speech-services/ をご参照ください
    • 1 か月あたり 0.5 million 文字まで無料
    • 「0.5 million 文字」というのは私にはあんまりピンときていませんが、毎朝ちょっとしたテキストを話すくらいなら全然だいじょうぶなのではないかとおもっちょります
    • 今日から使い始めますので、また1ヶ月後くらいに更新したいとおもいます

ソースコード

mix.exs
  defp deps do
    [
      ...
      {:httpoison, "~> 1.8"},
      {:jason, "~> 1.2"}
    ]
  end
lib/azure/text_to_speech.ex
defmodule Azure.TextToSpeech do
  @subscription_key "secret"
  @locale "ja-JP"
  @gender "Female"
  @voice_type "Neural"

  def run!(text) do
    access_token()
    |> voice(text)
  end

  def access_token do
    headers = [
      "Ocp-Apim-Subscription-Key": @subscription_key,
      "Content-type": "application/x-www-form-urlencoded"
    ]

    "https://eastus.api.cognitive.microsoft.com/sts/v1.0/issuetoken"
    |> HTTPoison.post!("", headers)
    |> Map.get(:body)
  end

  def voices_list(token) do
    "https://eastus.tts.speech.microsoft.com/cognitiveservices/voices/list"
    |> HTTPoison.get!(authorization_header(token))
    |> Map.get(:body)
    |> Jason.decode!()
  end

  def voice(token, text) do
    %{"Name" => name} = select_voice(token)

    headers =
      authorization_header(token)
      |> Keyword.merge(
        "Content-Type": "application/ssml+xml",
        "X-Microsoft-OutputFormat": "riff-24khz-16bit-mono-pcm",
        "User-Agent": "awesome"
      )

    "https://eastus.tts.speech.microsoft.com/cognitiveservices/v1"
    |> HTTPoison.post!(ssml(text, name), headers)
    |> Map.get(:body)
  end

  defp authorization_header(token) do
    [Authorization: "Bearer #{token}"]
  end

  defp select_voice(token) do
    voices_list(token)
    |> Enum.filter(fn %{"Locale" => l} -> l == @locale end)
    |> Enum.filter(fn %{"Gender" => g} -> g == @gender end)
    |> Enum.filter(fn %{"VoiceType" => vt} -> vt == @voice_type end)
    |> Enum.random()
  end

  defp ssml(text, name) do
    """
    <speak version='1.0' xml:lang='#{@locale}'>
      <voice xml:lang='#{@locale}' xml:gender='#{@gender}' name='#{name}'>
        <prosody volume="100.0">
          #{text}
        </prosody>
      </voice>
    </speak>
    """
  end
end
  • @subscription_keyにはAzureのポータル画面からキーを取得して値をセットしてください
  • 「同じトークンを 9 分間使用することをお勧めします」は未実装
    • たぶんAgentを使えばうまく書ける気がします
    • この記事では取り扱いません(サボり)

Run! :robot:

$ mix deps.get

$ iex -S mix

iex> (
Azure.TextToSpeech.run!("Azure最高です!。Microsoft Igniteに参加してイベントに関する記事を投稿しよう! 詳しくはこちらをご参照ください。https://qiita.com/official-events/a50e99d62dc62d68a9c9")
|> (&(File.write("output", &1))).()
)

iex> :os.cmd('afplay output')
  • 再生コマンドはmacOSの例です
  • Raspberry Piの場合は、 aplay -q /tmp/output な感じで再生してください
    • Nervesの場合は、/tmp/output 等に音声データを書き出してください

Wrapping Up :lgtm::lgtm::lgtm::lgtm::lgtm:

  • フレンドリーなヴォイスが得られました!
    • みなさんもお好きなプログラミング言語で、フレンドリーな音声を楽しんでみてください
  • Enjoy Elixir :bangbang::bangbang::bangbang:

最後の最後に

Elixirってなによ:interrobang:という方へ

image.png

EsvA7uQU0AEoTuX.jpeg

(@piacerex さん作 :pray::pray_tone1::pray_tone2::pray_tone3::pray_tone4::pray_tone5:)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
6