4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Amazon Polly を使う

Posted at

スライドを音声入りビデオにして送れ、と言われたので試してみたかった音声合成を試してみた。まあ、どう考えても私の英語よりは聞きやすいだろう。

Amazon Polly

Amazon Polly は、AWSの音声サービス。コンソールから使うこともできるが、AWSなので、APIでも使うことができる。関係ないけどAWSのサービスって、AWS XXX という場合と Amazon XXX という場合があるような気がするけど、どうやって使い分けてるんだろう。PollyにはAWSではなくAmazonがついている。

使い方

  1. AWS アカウントを用意する

  2. 権限としてPollyFullAccess のみを付与した Polly専用IAMを作り、ACCESS KEYと SECRET KEYを取得する。
    ルートアカウントで、ACCESS KEYを作ることももちろんできるが、まあ絶対やめたほうがいい。言うまでもないが、漏洩した場合のダメージを最小化するため。

  3. ~/.aws/credentials にこれらの値を書く。

    [default]
    aws_access_key_id = XXXXX
    aws_secret_access_key = XXXXX
    
  4. ~/.aws/config に下のようにリージョンを指定する。

     [default]
     region=us-east-1
    
  5. python3 と boto3 をインストールする。

    > pip install boto3
    

コード

使い方は思いの外簡単で、 ミニマルにはこんな感じ。

import boto3
client = boto3.client('polly')

response = client.synthesize_speech(
    OutputFormat='mp3',
    SampleRate='8000',
    Text='こんにちは、アマゾン ポリー です',
    TextType='text',
    VoiceId='Mizuki',
)

b = response['AudioStream'].read()
with open('test.mp3', "wb") as f:
    f.write(b)

VoiceIdとしてMizukiを選んでいるが、Idを指定することで、暗黙裡に言語と男性/女性を指定したことになっている。VoiceIdのリストは下のサイトにある。

TextとSSML

テキストで直接指定すると、話す速度を変更したり、文の一部を強調したり、ちょっと間を置くなどのこまかい制御ができない。これを可能にするにはSSMLというXML形式を使う必要がある。SSMLは標準らしくて、Googleなどの音声合成でも同じものが使えるようだ。方言が色々ありそうだけど。

SSMLを使う際には、TextTypessmlを指定し、Text<speak>タグで囲まれた文書を使えばいい。

response = client.synthesize_speech(
    OutputFormat='mp3',
    SampleRate='8000',
    Text='<speak>こんにちは、アマゾン ポリー です</speak>',
    TextType='ssml',
    VoiceId='Mizuki',
)

SSMLを使うと発話速度を調節できる。
AWS Pollyの合成音声はやや速めだと思うので、これが重要になる。
次のように書くと速度が90%になる。

<speak>
    <prosody rate="90%"> 
        こんにちは、アマゾン ポリー です
    </prosody>
</speak>

スクリプト

スライドの音声を合成するのが目的なので、1つのファイルでまとめて管理したい。ということで、簡単なフォーマットを考えてパーサを書いた。スクリプトは下に示す感じで、--- スライド名 で区切って文章を書く。スライド名はファイル名に使われる。また、1枚のスライドの中で、複数の音声を使うことも多いので、[click]で区切ると前後を別の音声ファイルとして扱うようにした。また、3つ以上連続した改行があった場合、空白をあけるようにしてある。

--- test1
1枚め

--- test2
2枚め

--- test3
3枚め
[click]
3枚めの2

このようなスクリプトに対して、

generate_mp3("script2.txt", targets=None, voice_id="Mizuki", speech_rate="60%")

のようにすると、test1-1.mp3, test2-1.mp3, test3-1.mp3, test3-2.mp3の4つのファイルができる。voice-id と発話速度を制御できる。

コードは以下のような感じ。

import boto3

DEFAULT_VOICE_ID="Matthew"

client = boto3.client('polly')

def generate_ssml(text, speech_rate):
    text = text.replace("\n\n\n\n", '<break time="2s"/>')
    text = text.replace("\n\n\n", '<break time="1.5s"/>')
    return '<speak><prosody rate="{}">{}</prosody></speak>'.format(speech_rate, text)

def speak_to_mp3(client, text, filename, voice_id=DEFAULT_VOICE_ID, speech_rate="100%"):
    response = client.synthesize_speech(
        OutputFormat='mp3',
        SampleRate='8000',
        Text=generate_ssml(text, speech_rate),
        TextType='ssml',
        VoiceId=voice_id
    )
    b = response['AudioStream'].read()
    with open(filename, "wb") as f:
        f.write(b)

def parse_script(filename, targets=None):
    ''' read the file. use --- as page separator, [click] as file separator.
    targets = "page1, page2" '''
    if targets:
        targets = [x.strip() for x in targets.split(',')]
    
    print(targets)
    d = {}  # key: filname, value: text
    section_counter = 1
    page = None
    content = ""
    
    def push_content():
        if (targets == None) or (page in targets):
            nonlocal content
            fn = "{}-{}.mp3".format(page, section_counter)
            d[fn] = content
        content = ""

    with open(filename, "r") as f:
        for l in f.readlines():
            if l.startswith("---"):
                if page:
                    push_content()
                section_counter = 1
                page = l.split()[-1]
                continue
            if l.startswith("[click]"):
                push_content()
                section_counter += 1
                continue
            content += l
    push_content()
    return d
        
def generate_mp3(script_file, targets=None, voice_id=None, speech_rate="90%"):
    d = parse_script(script_file, targets=targets)
    for filen, text in d.items():
        speak_to_mp3(client, text, filen, voice_id=voice_id, speech_rate=speech_rate)

所感

Polly の生成する音声の質は十分に高い。
生成された音声をPowerPoint に張り込んで、export でmp4ファイルを作成して出来上がり。ただ、アニメーションのタイミングをあわせたりなど、そんなに楽ではない。スライドをビデオにする方法として、全体として楽だったかどうかはかなり疑問かな。。

どうせやるならMarkdownやLatexでスライドを書いて、静止画像と音声データから、直接ビデオを生成するとかやったほうがいいかもしれない。そのうちやってみよう。

4
3
0

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
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?