この章では、読み上げパッケージのpyttsx3と、音声認識パッケージのWhisperを紹介します。読み上げパッケージではテキスト文字列を音声に変換し、コンピュータのスピーカーで再生させるか音声ファイルに保存するかします。プログラムで音声を生成できるようになれば、画面に表示されるテキストを追わなくてもよくなります。例えば、台所を動き回っている間にレシピアプリが材料リストを読んでくれますし、ニュース記事やメールを毎日MP3ファイルにして朝の通勤時に聞くことができます。
反対に、音声認識は音声ファイルをテキスト文字列値に変換します。声でプログラムに命令したり、ポッドキャストの文字起こしをしたりできます。コンピュータ相手なら、うるさいと感じたら音量を下げて黙らせることができます。
pyttsx3とWhisperの両方とも、インターネットに接続していなくても自由に使えます。本章で紹介する読み上げと音声認識は英語に限らず幅広い言語で利用できます。 (訳注:日本語対応には追加の設定が必要になることがあり、英語と比べると精度が劣ります。)
pyttsx3は音声を生成するのにOSの読み上げエンジンを利用します。WindowsならMicrosoft Speech API (SAPI5)、macOSならNSSpeechSynthesizer、LinuxならeSpeakです。Linuxではターミナルウィンドウからsudo apt install espeakを実行してeSpeakをインストールする必要があるかもしれません。pyttsx3はターミナルからpip install pyttsx3でインストールできます。サードパーティパッケージのインストールについては付録Aに詳しくまとめています。
pyttsx3という名前は、Pythonのpy、読み上げ(text-to-speech)のtts、オリジナルのpyttsパッケージから拡張されたことを示すx、Python 3の3に由来しています。
音声の生成にはコンピュータサイエンスの複雑な知識が求められますが、難しい部分はOSの読み上げエンジンが担当してくれるので、Pythonのスクリプトはシンプルです。新しいファイルエディタを開き、以下のコードをhello_tts.pyという名前で保存してください。
import pyttsx3
engine = pyttsx3.init()
engine.say('Hello. How are you doing?')
engine.runAndWait() # 音声の再生
feeling = input('>')
engine.say('Yes. I am feeling ' + feeling + ' as well.')
engine.runAndWait() # ここでも音声の再生
pyttsx3モジュールをインポートしてから、pyttsx3.init()関数を呼び出して読み上げエンジンを初期化しています。この関数はEngineオブジェクトを返します。このオブジェクトのsay()メソッドに読み上げるテキスト文字列を渡します。ただし、runAndWait()メソッドを呼び出すまで読み上げは行われません。このメソッドは文字列全部を読み上げるまでプログラム実行をブロックします。
このプログラムではprint()関数を呼び出していないのでテキストは出力されません。コンピュータが“Hello. How are you doing?”と再生するのが聞こえるはずです。もし何も聞こえなければ音量を調整してください。ユーザーがキーボードから返事を入力してエンターキーを押すと、コンピュータが“Yes. I am feeling <キーボードから入力した内容> as well.”と返答を再生します。
pyttsx3ではコンピュータが生成する音声の速度や音量などを調整できます。EngineオブジェクトのgetProperty()メソッドに'rate'、'volume'、'voices'の文字列を渡せば現在の設定を確認できます。以下の式を対話型シェルに入力してみてください。
>>> import pyttsx3
>>> engine = pyttsx3.init()
>>> engine.getProperty('volume')
1.0
>>> engine.getProperty('rate')
200
>>> engine.getProperty('voices')
[<pyttsx3.voice.Voice object at 0x0000029DA7FB4B10>,
<pyttsx3.voice.Voice object at 0x0000029DAA3DAAD0>]
出力結果はお使いのコンピュータによって異なります。音量は浮動小数点数値で、1.0が100パーセントを表しています。速度は1分間に200語の速さです。以下のコードを続けてください。
>>> for voice in engine.getProperty('voices'): # 利用できる声のリスト
... print(voice.name, voice.gender, voice.age, voice.languages)
...
Microsoft David Desktop - English (United States) None None []
Microsoft Zira Desktop - English (United States) None None []
英語(合衆国)に設定されている私のWindowsノートパソコンでは、getProperty('voices')が2つのVoiceオブジェクトを返しました(単数形の'voice'ではなく複数形の'voices'であることに注意してください)。このVoiceオブジェクトにはname、gender、ageの属性がありますが、genderとageはOSによりNoneに設定されています。languages属性は対応している言語の文字列のリストです。その情報が設定されていなければ空のリストになります。
対話型シェルの例を続けてsetProperty()メソッドでこれらの設定を変更してみましょう。
>>> engine.setProperty('rate', 300)
>>> engine.setProperty('volume', 0.5)
>>> voices = engine.getProperty('voices')
>>> engine.setProperty('voice', voices[1].id)
>>> engine.say('The quick brown fox jumps over the yellow lazy dog.')
>>> engine.runAndWait()
この例では、最初の2行で、1分間に300語の速さ、50パーセントの音量に変更しています。それから、getProperty('voices')が返すリストのインデックス1のVoiceオブジェクトのid属性を渡すことで、Windowsが用意している女性の"Zira"の声に変更しています。声の設定は複数形の'voices'ではなく単数形の'voice'を使うことに注意してください。
生成した音声をWAVファイル(.wavファイル拡張子)に保存するには、pyttsx3モジュールのsave_to_file()メソッドを使います。以下の式を対話型シェルに入力してみてください。
>>> import pyttsx3
>>> engine = pyttsx3.init()
>>> engine.save_to_file('Hello. How are you doing?', 'hello.wav')
>>> engine.runAndWait() # hello.wavファイルの作成
save_to_file()の第一引数は読み上げる文字列で、第二引数は.wavファイルのファイル名です。読み上げる文字列はこの対話型シェルの例のように短くても、何ページにも及ぶほどの長さでも構いません。私のPCでは、pyttsx3が2秒ほどで1,800語の文字列を約10分の音声ファイルに変換しました。save_to_file()を呼び出すだけでは音声ファイルが作成されません。runAndWait()メソッドを呼び出して初めて.wavファイルが作成されます。
pyttsx3モジュールは.wavファイルにだけ対応しており、.mp3その他の音声形式には対応していません。
Whisperは複数の言語を認識できる音声認識システムです。音声ファイルや動画ファイルからPythonの文字列でテキストを返します。テキストのまとまりごとに開始時間と終了時間も返してくれるので、字幕ファイルを作成するのに便利です。
ターミナルでpip install openai-whisperを実行すればWhisperをインストールできます(音声認識のパッケージはopenai-whisperであり、whisperとは別のパッケージです)。サイズが大きくインストールするのに数分かかるかもしれません。また、load_model()関数の初回呼び出し時には、数百メガバイト以上のサイズの音声認識モデルをダウンロードします。
現在の作業ディレクトリにhello.wavという名前の音声ファイルがあるとします(Whisperは.mp3など他の音声形式も扱えます)。以下の内容を対話型シェルで実行してみましょう。
>>> import whisper
>>> model = whisper.load_model('base')
>>> result = model.transcribe('hello.wav')
>>> print(result['text'])
Hello. How are you doing?
whisperモジュールをインポートしてから、'tiny'、'base'、'small'、'medium'、'large-v3'のいずれかの機械学習モデル名(将来的に新しいモデルが利用できるようになるかもしれません)の文字列を渡してwhisper.load_model()関数を呼び出し、使用する音声認識モデルをロードします。小さいモデルは高速であり、大きいモデルは雑音が入っていても正確に認識してくれます。
モデルを初めてロードするときには、whisperモジュールがOpenAIのサーバーからモデルをダウンロードするのでインターネットに接続していなければなりません。whisper.load_model()関数に渡すモデル名の文字列とサイズ、メモリ使用量、私のノートパソコンでの実行結果を表24-1にまとめました。
モデル名 |
モデルのファイルサイズ |
メモリ使用量 |
3分で10語の音声サンプルの実行時間 |
15分で1,800語の音声サンプルの実行時間 |
|---|---|---|---|---|
'tiny' |
74MB |
1GB |
1.9s |
1m 20s |
'base' |
142MB |
1GB |
3.0s |
2m 34s |
'small' |
472MB |
2GB |
9.6s |
6m 37s |
'medium' |
1.5GB |
5GB |
28.6s |
20m 17s |
'large-v3' |
3GB |
10GB |
51.9s |
32m 58s |
個人的には、基本的に'base'で十分で'medium'ならより正確だと感じています。どのモデルを使っても誤りは発生しますから、出力を人間がチェックしたほうがよいでしょう。誤りが多ければより大きなモデルを、音声認識に時間がかかりすぎればより小さなモデルを使ってください。表24-1に示したように、15分の音声ファイルを認識するのに、'base'モデルでは2分34秒、'large-v3'モデルでは約33分かかりましたから、所要時間には大きな違いがあります。
whisper.load_model()が返すmodel.Whisperオブジェクトについてtranscribe()メソッドを呼び出すと実際の音声認識が実行されます。このメソッドには音声ファイルの名前を文字列で渡します。このメソッドの実行には、モデルと音声ファイルの長さ次第で数秒から数時間かかります。Whisperは音声ファイルと動画ファイルの形式は問わず自動的に形式を変換してくれます。
Whisperは認識する音声の言語を自動的に判別しますが、model.transcribe('hello.wav', language='English')のようにキーワード引数で言語を指定することができます。ターミナルでwhisper --helpを実行すると対応する言語の一覧を確認できます。完璧ではないにせよ句読点や大文字小文字の判定をしてくれます。それでも誤りを修正するためにチェックしたほうがよいです。
model.transcribe()は辞書を返します。その辞書の'text'キーで認識結果の文字列にアクセスできます。
WhisperはデフォルトでCPUを使って音声を認識します。グラフィックスカードが搭載されているコンピュータならGPUを使う設定にすると音声認識の速度を大いに向上させられます。https://
オンラインドキュメントではその他のオプションについても説明されています。
Whisperが返す辞書には、音声を認識したテキスト文字列だけでなく、そのテキストが音声ファイルのどこに当たるのかという時間情報も含まれています。このテキストと時間情報を利用すれば字幕ファイルを作成でき、他のソフトウェアで活用することができます。字幕ファイルで一般的な形式はSRT(拡張子は.srt)とVTT(拡張子は.vtt)です。SRTは比較的古くて広く用いられているのに対し、VTTは新しい形式です。両者の形式は似ています。例えば、SRTファイルは次のような形です。
1
00:00:00,000 --> 00:00:05,640
Dinosaurs are a diverse group of reptiles of the clade dinosauria. They first
2
00:00:05,640 --> 00:00:14,960
appeared during the triassic period. Between 245 and 233.23 million years ago.
--snip--
VTTファイルは次のような形です。
WEBVTT
00:00.000 --> 00:05.640
Dinosaurs are a diverse group of reptiles of the clade dinosauria. They first
00:05.640 --> 00:14.960
appeared during the triassic period. Between 245 and 233.23 million years ago.
--snip--
どちらのファイルからも、“Dinosaurs are a diverse group ...”が音声ファイルの冒頭(0秒)から5.640秒の間であることがわかります。
WhisperはTSVデータ(拡張子は.tsv)またはJSONデータ(拡張子は.json)として結果を出力することもできます。TSVは字幕ファイルの形式ではありませんが、テキストと時間情報をエクスポートして別のPythonプログラムで読み取る場合などに便利です。第18章で紹介したcsvモジュールでTSVファイルを読み取れます。TSVは以下のような形式です。
start end text
0 5640 Dinosaurs are a diverse group of reptiles of the clade dinosauria. They
5640 14960 appeared during the triassic period. Between 245 and 233.23 million years ago.
--snip--
字幕ファイルを作成するときはmodel.transcribe()を呼び出してから次のような2行のコードを書きます。
>>> import whisper
>>> model = whisper.load_model('base')
>>> result = model.transcribe('hello.wav')
❶ >>> write_function = whisper.utils.get_writer('srt', '.')
❷ >>> write_function(result, 'audio')
whisper.utils.get_writer()関数は字幕ファイルの形式を表す文字列('srt'、'vtt'、'txt'、'tsv'、'json')とファイルを保存するフォルダ('.'は現在の作業ディレクトリという意味です)を引数に取ります(❶)。get_writer()関数は、認識結果を渡す関数を返します(関数が関数を返すというのは一風変わった方法のように思われますが、whisperモジュールの設計ではそうなっています)。ここではwrite_functionという名前の変数にその認識結果を渡す関数を格納し、この変数を関数として用いて辞書resultと字幕ファイルの名前を渡して呼び出しています(❷)。この2行により、辞書resultに含まれるテキストと時間情報から、audio.srtという名前のSRT形式の字幕ファイルが現在の作業ディレクトリに作成されます。
(サンプル音声ダウンロードサイト等から)Whisperによる音声認識用の音声ファイルをダウンロードするのは簡単ですが、YouTubeのようなウェブサイトから動画をダウンロードするのは簡単ではありません。yt-dlpモジュールを使うとYouTubeその他の動画サイトからPythonスクリプトで動画をダウンロードしてオフラインでも視聴できます。付録Aの指示に沿ってyt-dlpをインストールしてください。以下のコードで指定したURLの動画をダウンロードできます。
>>> import yt_dlp
>>> video_url = 'https://www.youtube.com/watch?v=kSrnLbioN6w'
>>> with yt_dlp.YoutubeDL() as ydl:
... ydl.download([video_url])
...
ydl.download()関数は動画URLのリストを引数に取るので、video_urlの文字列をリストの中に入れてこの関数に渡しています。動画ファイルの名前は動画サイト上でのタイトルに.mp4や.mkvなどのファイル拡張子をつけたものになります。動画をダウンロードする際にはデバッグ情報が大量に表示されます。
動画サイトは、年齢、ログイン、地理的制限、ウェブスクレイピング対策などの理由でダウンロードを拒否することがあります。エラーが発生したら、まずyt-dlpの最新版をインストールしてみてください。yt-dlpは動画サイトの変更に対応できるように更新されています。
https://
>>> import yt_dlp
>>> video_url = 'https://www.youtube.com/watch?v=kSrnLbioN6w'
>>> options = {
... ❶ 'quiet': True, # 出力の抑制
... 'no_warnings': True, # 警告の抑制
... ❷ 'outtmpl': 'downloaded_content.%(ext)s',
... 'format': 'm4a/bestaudio/best',
... 'postprocessors': [{ # ffmpegを使って音声抽出
... 'key': 'FFmpegExtractAudio',
... 'preferredcodec': 'm4a',
... }]
...}
...
>>> with yt_dlp.YoutubeDL(options) as ydl:
... ydl.download([video_url])
...
yt_dlp.Youtube()にoptions辞書を渡して各種設定を行います。まず、'quiet': Trueと'no_warnings': Trueにより冗長なデバッグ出力を抑制しています(❶)。次に、動画をダウンロードしてから音声を抽出してdownloaded_content.m4aという名前のファイルに保存するように指示しています(❷)(ファイル拡張子は動画の音声形式により異なるかもしれませんが、.m4a形式が最も一般的です)。options辞書に'outtmpl': 'downloaded_content.%(ext)s'と設定しなければ、ダウンロードしたファイルの名前は動画のタイトルになります(クエスチョンマークやコロンなどのファイル名に使えない文字は除外されます)。
第10章で説明したグロブパターンを使えば、ファイル名の一部から完全なファイル名を取得できます。抽出した音声のファイル名が'downloaded_content'で始まることはわかっていますが、拡張子は音声形式により異なります。以下のコードではPathオブジェクトを使ってダウンロードしたファイル名を取得しています。
>>> from pathlib import Path
>>> matching_filenames = list(Path().glob('downloaded_content.*'))
>>> downloaded_filename = str(matching_filenames[0])
>>> downloaded_filename
'downloaded_content.m4a'
ファイル名を指定すると、Whisperで音声認識を実行するといった後処理をしやすくなります。'base'モデルや'medium'モデルを使えばYouTubeが自動生成する字幕よりも高品質の字幕を作成できます。
指定した動画の情報がほしいだけなら、以下のようなコードを書けば、ファイルをダウンロードせずにメタデータだけを取得できます。
>>> import yt_dlp, json
>>> video_url = 'https://www.youtube.com/watch?v=kSrnLbioN6w'
>>> options = {
... 'quiet': True, # 出力の抑制
... 'no_warnings': True, # 警告の抑制
... ❶ 'skip_download': True, # 動画をダウンロードしない
...}
...
>>> with yt_dlp.YoutubeDL(options) as ydl:
... ❷ info = ydl.extract_info(video_url)
... ❸ json_info = ydl.sanitize_info(info)
... print('TITLE:', json_info['title']) # 動画のタイトルを表示
... print('KEYS:', json_info.keys())
... with open('metadata.json', 'w', encoding='utf-8') as json_file:
... ❹ json_file.write(json.dumps(json_info))
...
TITLE: Beyond the Basic Stuff with Python - Al Sweigart - Part 1
KEYS: dict_keys(['id', 'title', 'formats', 'thumbnails', 'thumbnail',
'description', 'channel_id', 'channel_url', 'duration', 'view_count',
'average_rating', 'age_limit', 'webpage_url',
--snip--
動画ファイルは不要で動画のメタデータだけが必要なら、yt_dlp.YoutubeDL()に渡すoptions辞書に'skip_download': Trueを含めます(❶)。ydl.extract_info()メソッドを呼び出すとその動画についての情報を含む辞書が返されます(❷)。正しいJSON形式(JSONについては第18章で説明しました)になっていない場合がありますが、ydl.sanitize_info()を呼び出すとJSON形式に対応させられます(❸)。sanitize()メソッドが返す辞書には、動画の名前を示す'title'、動画の長さを秒数で示す'duration'などのキーがあります。上記のコードではこのJSONデータをmetadata.jsonという名前のファイルに書き出しています(❹)。
Pythonには、読み上げや音声認識など、豊富なサードパーティパッケージのエコシステムが広がっているという大きな強みがあります。こうしたサードパーティパッケージがコンピュータサイエンスの最も困難な部分の仕事を引き受けてくれるので、自分ではコードを数行書くだけでその機能を利用できます。
pyttsx3パッケージはOSの読み上げエンジンを利用して読み上げを行い、スピーカーから再生したり.wavファイルに保存したりできます。Whisper音声認識システムは機械学習モデルを利用して音声ファイルからテキストを書き出します。モデルは複数あり、小さなモデルは高速ですが精度が低く、大きなモデルは低速ですが精度が高いです。英語だけでなく多くの自然言語に対応しています。Whisperは初回にダウンロードするとき以外はオフラインで実行できます。
これらのPythonパッケージが利用している音声エンジンは2020年代に急速に発展しました。Pythonは自作のスクリプトでこうしたソフトウェアを利用するのに最適なグルー(糊)言語ですから、コードを数行書き足すだけで自分のプログラムに読み上げ機能を追加できます。読み上げと音声認識についてもっと詳しく知りたければ、Mark LiuのMake Python Talk(No Starch Press, 2021)におもしろい例がたくさん載っています。
1. pyttsx3の読み上げ速度を上げるにはどうしますか?
2. pyttsx3が保存する音声形式は何ですか?
3. pyttsx3とWhisperはオンラインサービスに依存していますか?
4. pyttsx3とWhisperは英語以外の言語に対応していますか?
5. Whisperのデフォルトの機械学習モデルの名前は何ですか?
6. 字幕ファイルの2つの一般的な形式は何ですか?
7. yt-dlpはYouTube以外のサイトから動画をダウンロードできますか?
以下の練習プログラムを書いてください。
第3章の数当てゲームに音声機能を追加してください。print()関数呼び出しをすべてspeak()関数呼び出しに変更します。それから、print()関数と同じようにその文字列を画面に表示するとともにスピーカーでも再生する、文字列を引数に取るspeak()関数を定義します。例えば、次の行を
print('I am thinking of a number between 1 and 20.')
次のように変更します。
speak('I am thinking of a number between 1 and 20.')
読み上げ機能を最大限活用して、'Your guess is too low.'や'Your guess is too high.'のテキストを、“Your guess, 42, is too low.”のようにプレイヤーが実際に推測した数を含めるように変更してください。じゃんけんなど本書の他のプロジェクトにも読み上げ機能を追加できます。
ここで言う数え歌とは、数字を数え上げるように歌詞を繰り返しながら少しずつ変えていく歌のことです。"99 Bottles of Beer"や"The 12 Days of Christmas"がその例です。"99 Bottles of Beer"を歌う(読み上げる)プログラムを書いてください。
99 bottles of beer on the wall,
99 bottles of beer,
Take one down, pass it around,
98 bottles of beer on the wall.
ビール瓶(bottles of beer)が一本ずつ少なくなっていく歌詞です。ビール瓶がなくなる最後の行は"No more bottles of beer on the wall."になります(99本ではなく2, 3本のビール瓶から始めたほうが簡単にテストできます)。
yt-dlpとWhisperの機能を組み合わせて、YouTubeから動画をダウンロードして.srt形式の字幕ファイルを自動的に生成するプログラムを書いてください。ダウンロードして字幕を生成する動画のURLのリストを入力します。字幕ファイルのフォーマットをオプションで指定できるように拡張することもできます。Pythonはさまざまなモジュールの機能を組み合わせるのに最適なグルー(糊)言語です。