19日に更新してた

アフィリエイトはないよ

Windows のテキスト読み上げを使って外国語の単語聞き取り用のファイルを作ってみた[win32com,python]

Qiita を徘徊していたら、Windows10 の Speech API を使う内容が書かれていたので、「これって単語帳の音声ファイルを自由に作れるのでは?」と思って早速やってみた。

qiita.com

import os
import win32com.client
import subprocess
import pandas as pd

# 保存するディレクトリ
wd=r"d:\wordlist"
# 保存したエクセルファイル
wordlist_file=r"d:\wordlist\Book1.xlsx"
# エクセル1列目の話者
speaker1 = "Microsoft David"
# エクセル2列目の話者
speaker2 = "Microsoft Sayaka"
# エクセル1列目の言語の略号 ディレクトリやファイル名に使う
lang1 = "e"
# エクセル2列目の言語の略号 ディレクトリやファイル名に使う
lang2 = "j"
# range(volume) 0(low) - 100(loud)
volume = 100
# rate(speed) -10(slow) - 10(fast)
# エクセル1列目の言語のスピード
rate1 = -2
# エクセル2列目の言語のスピード
rate2 = 0
# 単語番号の桁数 例 digit = 3 : 001 , digit = 5 : 00001
digit = 4
# 音声のあとの無音時間(秒)
duration = 1

df=pd.read_excel(wordlist_file,header=None)

def filename(text):
    taboo_file_name_text_dic={k:None for k in ["<",">",":","\\","/","|","?","*"]}
    tbl=taboo_file_name_text_dic
    text =  text.translate(str.maketrans(tbl))
    return text[:23]

def make_speech_wav_win(word,speaker,filename,speed,volume,directory="."):
    import win32com.client
    import os
    
    sapi = win32com.client.Dispatch("SAPI.SpVoice")
    cat  = win32com.client.Dispatch("SAPI.SpObjectTokenCategory")
    cat.SetID(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices", False)
    fs = win32com.client.Dispatch("SAPI.SpFileStream")

    fs.Open(os.path.join(directory,f"{filename}.wav"), 3)
    sapi.AudioOutputStream = fs
    sapi.rate = speed
    sapi.volume = volume
    oldv = sapi.Voice
    sapi.Voice = [t for t in cat.EnumerateTokens() if t.GetAttribute("Name") == speaker][0]
    sapi.Speak(word)
    sapi.Voice = oldv
    fs.Close()
    
cwd=os.getcwd()
os.makedirs(wd,exist_ok=True)
os.chdir(wd)

lang12=lang1+lang2
os.makedirs(lang12,exist_ok=True)
dirlist12=os.listdir(lang12)

lang21=lang2+lang1
os.makedirs(lang21,exist_ok=True)
dirlist21=os.listdir(lang21)

if not dirlist21 or not dirlist12:
    start_number=0
else:
    start_number=min(max([int(t.split("_")[0]) for t in dirlist12]),max([int(t.split("_")[0]) for t in dirlist21]))

for n_,n in enumerate(range(len(df)),1):
    if n_ <= start_number:
        continue
    
    make_speech_wav_win(df.at[n,0],speaker1,f"temp_{lang1}",rate1,volume)
    subprocess.run(f'ffmpeg -i temp_{lang1}.wav -af "apad=pad_dur={duration}" temp_{lang1}2.wav')
    
    make_speech_wav_win(df.at[n,1],speaker2,f"temp_{lang2}",rate2,volume)
    subprocess.run(f'ffmpeg -i temp_{lang2}.wav -af apad=pad_dur={duration} temp_{lang2}2.wav')

    subprocess.run(f'wsl sox temp_{lang1}2.wav temp_{lang2}2.wav temp_{lang12}.wav')
    subprocess.run(f'ffmpeg -i temp_{lang12}.wav  -f mp4 ".\\{lang12}\\{n:0{digit}}_{filename(df.at[n,0])}_{lang1}.m4a"')
    
    subprocess.run(f'wsl sox temp_{lang2}2.wav temp_{lang1}2.wav temp_{lang21}.wav')
    subprocess.run(f'ffmpeg -i temp_{lang21}.wav -f mp4 ".\\{lang21}\\{n:0{digit}}_{filename(df.at[n,1])}_{lang2}.m4a"')

    for file in os.listdir():
        if 'temp' in file:
            os.remove(file)

os.chdir(cwd)
print("終了")

win11、Jupiter notebook 上で '3.8.12 (default, Oct 12 2021, 03:01:40) [MSC v.1916 64 bit (AMD64)]' で動かしております。*1

エクセルファイルに英語(音声ファイルをアメリカ英語にしているので以下米語)、日本語で入れて保存したものをつかってテストして、自分で入力する場合に数回に渡って入力した場合にも続きから作成するようにしました。発音スピードも米語と日本語の間の無音時間の設定も見たとおり。

作成されるファイルは1列目の言語(米語)、無音 ( duration 秒 )、2列目の言語(日本語)、無音 ( duration 秒 ) の構成からなるファイル、と、2列目の言語、無音、1列目の言語、無音の構成からなるファイル。1単語対から2ファイル作られます。

文章読み上げを使ってますので、単語だけでなく文章でもいけます。

Windows のパスと wsl のパスを混在させるのは面倒なので Windows メインで動かせるようにしてあります。*2

ファイルネームはもともとのエクセル上の文字列23文字*3を使っておりますが、禁止文字は一応削除してあるはずで、禁止ファイルネームは最初に番号を最後に e、j を入れることで避けられているはずですので特にチェックはしておりません。
mp4 にエンコードする時にダブルクオート (") でくくってあるので、文章を入力した場合でもスペース ( ) がファイル名に反映されてます、僕の環境では。

一応、テストは米日、日米でやっておりますが、Microsoft のテキスト読み上げ言語をダウンロードするWindows 10にある種類同士ならダウンロード、インストールしたあとで、上のページに有る voice2.py を参照してちょこちょこっと書き換えれば可能なはずなので、まぁうまいことやってください。あと、話者の変更も同じですね。

support.microsoft.com

「コンピュータの発音は...」とか言われる方もいらっしゃると思いますが、少なくとも僕の外国語よりは余裕で通じるはず & コスト0なのでバンバン使い倒さないとと思いつつ、全然勉強していないので説得力が...

*1:ffmpeg が Windows11 上で動いていたり、sox が wsl 上で動いていたりしますが、まぁそういうことで。ffmpeg で concat が上手くできなかったのと Windowssox インストールする方法を検索するのが面倒だった。

*2:それでも面倒くさいですが

*3:ファイルネーム部分で30文字くらいがいいかなと思ってなんとなく23文字