Open JTalk
テキストを音声で読み上げてほしいので、Open JTalkをインストールしました。誤字脱字を探すのに、とても役立ちます。
ソフトウェアマネージャーで「open-jtalk」で検索します。「openjtalk」で検索すると検索に失敗します。
ソフトウェアマネージャーでOpen-jtalkとOpen-jtalk-mecab-naist-jdicの2つをインストールします。
Open-jtalk-mecab-naist-jdicは漢字の読み方の辞書のデータだそうです。
meiという女性の音声のデータをダウンロードします。著者の場合はブラウザでダウンロードしました。
MMDAgent_Example-1.7.zipを展開(解凍)します。MMDAgent_Example-1.7ディレクトリが出来るはずです。
Open JTalkの動作確認のため、「端末」というアプリケーションで次のようなコマンドを実行します。MMDAgent_Example-1.7ディレクトリがデスクトップに有る場合のコマンドです。◯◯を自分のユーザ名に変えて実行してください。
――――――――――――――――――――
echo 'あなたを愛しています。' | open_jtalk -x /var/lib/mecab/dic/open-jtalk/naist-jdic -m /home/◯◯/デスクトップ/MMDAgent_Example-1.7/Voice/mei/mei_happy.htsvoice -ow /home/◯◯/デスクトップ/ILoveYou.wav -r 0.8
――――――――――――――――――――
Open JTalkは1024バイト以内の文字を読み上げて、1025バイト以降の文字を無視し正常終了します。
普通に改行していれば、長くても、1行の文字の量が1024バイトを超過する事は無いと思って良いです。
Open JTalkに文字数が多い文字を読み上げさせると、間延びしたように、言葉に詰まったように、言葉を
この不具合を直したC言語?のソース コードのパッチを公開してくれている人がいるのですが、このパッチを適用する場合、Open JTalkのソース コードなどをダウンロードしてからパッチを上書きしてソース コードをコンパイルする必要が有ります。
コマンドだけでは不便なので、Pythonでテキストエリアのテキストを読み上げるコードを作成してみました。
GithubでPythonのソース コードをパブリック ドメインで公開しております。
マイクロソフトのBing検索エンジンで「github eliphas1810-tools」などで検索してみてください。
残念ながらグーグル検索エンジンでは検索できません。
コピペする場合は2文字の全角空白を4文字の半角空白に置換してください。
Linux MintにはPythonが標準でプリインストールされているので、何もしなくてもPythonが存在するはずです。
tkinterというGUIライブラリがPythonには標準で含まれている……はずなのですが、著者の場合は、なぜかインストールする必要がありました。ソフトウェアマネージャでPython3-tkをインストールしました。
例えばxxx.pyという名前のファイルで保存して、端末というアプリケーションでpython3 xxx.pyと入力して実行してください。
――――――――――――――――――――
import tkinter
from tkinter import filedialog
import tempfile
import re
import subprocess
import os
import threading
root = tkinter.Tk()
root.geometry("700x600")
root.title("テキストエリアの文字をOpenJTalkで読み上げます。")
message_string_var = tkinter.StringVar()
message_string_var.set("")
message_label = tkinter.Label(root, textvariable=message_string_var)
message_label.place(x=10, y=550)
file_string_var = tkinter.StringVar()
file_string_var.set("")
file_label = tkinter.Label(root, textvariable=file_string_var)
file_label.place(x=10, y=40)
# .htsvoiceファイル選択ボタンが押された時の処理
def select_file():
message_string_var.set("")
voice_file_path = filedialog.askopenfilename(title=".htsvoiceファイル選択", multiple=False, filetypes=[(".htsvoiceファイル", ".htsvoice")])
if "'" in voice_file_path:
message_string_var.set("'が含まれない.htsvoiceファイルを選択してください。")
return
file_string_var.set(voice_file_path)
file_select_button = tkinter.Button(root, text=".htsvoiceファイル選択", command=select_file)
file_select_button.place(x=10, y=10)
textarea = tkinter.Text(width=80, height=30)
textarea.place(x=10, y=70)
vertical_scrollbar = tkinter.Scrollbar(root, orient=tkinter.VERTICAL)
vertical_scrollbar.place(x=580, y=70, height=430)
vertical_scrollbar.config(command=textarea.yview)
textarea.config(yscrollcommand=vertical_scrollbar.set)
cancel_flag = False
# 読み上げ処理
def read():
global cancel_flag
cancel_flag = False
message_string_var.set("")
voice_file_path = file_string_var.get()
if voice_file_path == "":
message_string_var.set(".htsvoiceファイルを選択してください。")
return
# if "'" in voice_file_path:
# message_string_var.set("'が含まれない.htsvoiceファイルを選択してください。")
# return
# 一時ディレクトリを作成
temp_dir = tempfile.TemporaryDirectory()
text = textarea.get("1.0", "end -1c")
line_list = text.splitlines()
for line in line_list:
if cancel_flag:
message_string_var.set("中止しました。")
break
# 全角等号を「は」に置換
line = re.sub("=", "は", line)
# 漢字、ひらがな、カタカナ、数字、アルファベット、一部の全角記号以外を全角空白に置換
line = re.sub("[^一-鿋々ぁ-ゖァ-ヺヲ-゚ー0-90-9a-zA-Za-zA-Z+×÷]", " ", line)
# 先頭の半角空白と全角空白の連続を除去
line = re.sub("^[ ]+", "", line)
# 末尾の半角空白と全角空白の連続を除去
line = re.sub("[ ]+$", "", line)
# 半角空白と全角空白の連続を全角空白に置換
line = re.sub("[ ]{2,}", " ", line)
if line == "":
continue
# 全角空白の連続の場合
if re.match("^ +$", line):
continue
subprocess.call("echo '" + line + "' | open_jtalk -x /var/lib/mecab/dic/open-jtalk/naist-jdic -m '" + voice_file_path + "' -ow '" + os.path.join(temp_dir.name, "temp.wav") + "' -r 0.8", shell=True)
subprocess.call("aplay -q '" + os.path.join(temp_dir.name, "temp.wav") + "'", shell=True)
temp_dir.cleanup()
message_string_var.set("読み上げが終わりました。")
# 読み上げボタンが押された時の処理
def run_read():
thread = threading.Thread(target=read)
thread.start()
read_button = tkinter.Button(root, text="読み上げ", command=run_read)
read_button.place(x=10, y=510)
# キャンセルボタンが押された時の処理
def cancel():
global cancel_flag
cancel_flag = True
cancel_button = tkinter.Button(root, text="中止", command=cancel)
cancel_button.place(x=100, y=510)
root.mainloop()
――――――――――――――――――――
さらに、全テキストの読み上げ音声の.mp3形式での保存機能を追加した物が次に成ります。
ただし、.wavファイルの連結と.mp3形式での保存に必要なPython3-pydubをソフトウェアマネージャーでインストールする必要が有ります。
――――――――――――――――――――
import tkinter
from tkinter import filedialog
import tempfile
import re
import subprocess
import os
import threading
from pydub import AudioSegment
import datetime
root = tkinter.Tk()
root.geometry("700x700")
root.title("テキストエリアの文字をOpenJTalkで読み上げます。")
message_string_var = tkinter.StringVar()
message_string_var.set("")
message_label = tkinter.Label(root, textvariable=message_string_var)
message_label.place(x=10, y=550)
file_string_var = tkinter.StringVar()
file_string_var.set("")
file_label = tkinter.Label(root, textvariable=file_string_var)
file_label.place(x=10, y=40)
# .htsvoiceファイル選択ボタンが押された時の処理
def select_file():
message_string_var.set("")
voice_file_path = filedialog.askopenfilename(title=".htsvoiceファイル選択", multiple=False, filetypes=[(".htsvoiceファイル", ".htsvoice")])
if "'" in voice_file_path:
message_string_var.set("'が含まれない.htsvoiceファイルを選択してください。")
return
file_string_var.set(voice_file_path)
file_select_button = tkinter.Button(root, text=".htsvoiceファイル選択", command=select_file)
file_select_button.place(x=10, y=10)
textarea = tkinter.Text(width=80, height=30)
textarea.place(x=10, y=70)
vertical_scrollbar = tkinter.Scrollbar(root, orient=tkinter.VERTICAL)
vertical_scrollbar.place(x=580, y=70, height=430)
vertical_scrollbar.config(command=textarea.yview)
textarea.config(yscrollcommand=vertical_scrollbar.set)
cancel_flag = False
# 読み上げ処理
def read():
global cancel_flag
cancel_flag = False
message_string_var.set("")
voice_file_path = file_string_var.get()
if voice_file_path == "":
message_string_var.set(".htsvoiceファイルを選択してください。")
return
# if "'" in voice_file_path:
# message_string_var.set("'が含まれない.htsvoiceファイルを選択してください。")
# return
# 一時ディレクトリを作成
temp_dir = tempfile.TemporaryDirectory()
text = textarea.get("1.0", "end -1c")
line_list = text.splitlines()
for line in line_list:
if cancel_flag:
message_string_var.set("中止しました。")
break
# 全角等号を「は」に置換
line = re.sub("=", "は", line)
# 漢字、ひらがな、カタカナ、数字、アルファベット、一部の全角記号以外を全角空白に置換
line = re.sub("[^一-鿋々ぁ-ゖァ-ヺヲ-゚ー0-90-9a-zA-Za-zA-Z+×÷]", " ", line)
# 先頭の半角空白と全角空白の連続を除去
line = re.sub("^[ ]+", "", line)
# 末尾の半角空白と全角空白の連続を除去
line = re.sub("[ ]+$", "", line)
# 半角空白と全角空白の連続を全角空白に置換
line = re.sub("[ ]{2,}", " ", line)
if line == "":
continue
# 全角空白の連続の場合
if re.match("^ +$", line):
continue
subprocess.call("echo '" + line + "' | open_jtalk -x /var/lib/mecab/dic/open-jtalk/naist-jdic -m '" + voice_file_path + "' -ow '" + os.path.join(temp_dir.name, "temp.wav") + "' -r 0.8", shell=True)
subprocess.call("aplay -q '" + os.path.join(temp_dir.name, "temp.wav") + "'", shell=True)
temp_dir.cleanup()
message_string_var.set("読み上げが終わりました。")
# 読み上げボタンが押された時の処理
def run_read():
thread = threading.Thread(target=read)
thread.start()
read_button = tkinter.Button(root, text="読み上げ", command=run_read)
read_button.place(x=10, y=510)
# キャンセルボタンが押された時の処理
def cancel():
global cancel_flag
cancel_flag = True
cancel_button = tkinter.Button(root, text="中止", command=cancel)
cancel_button.place(x=100, y=510)
save_dir_string_var = tkinter.StringVar()
save_dir_string_var.set("")
save_dir_label = tkinter.Label(root, textvariable=save_dir_string_var)
save_dir_label.place(x=10, y=610)
# 保存ディレクトリ選択ボタンが押された時の処理
def select_save_dir():
save_dir = filedialog.askdirectory(initialdir=os.path.expanduser("~"))
save_dir_string_var.set(save_dir)
save_dir_select_button = tkinter.Button(root, text="保存ディレクトリ選択", command=select_save_dir)
save_dir_select_button.place(x=10, y=580)
# 保存ボタンが押された時の処理
def save():
message_string_var.set("")
save_dir = save_dir_string_var.get()
if save_dir == "":
message_string_var.set("保存ディレクトリを選択してください。")
return
voice_file_path = file_string_var.get()
if voice_file_path == "":
message_string_var.set(".htsvoiceファイルを選択してください。")
return
# if "'" in voice_file_path:
# message_string_var.set("'が含まれない.htsvoiceファイルを選択してください。")
# return
# 一時ディレクトリを作成
temp_dir = tempfile.TemporaryDirectory()
text = textarea.get("1.0", "end -1c")
line_list = text.splitlines()
wav_count = 0
for line in line_list:
# 全角等号を「は」に置換
line = re.sub("=", "は", line)
# 漢字、ひらがな、カタカナ、数字、アルファベット、一部の全角記号以外を全角空白に置換
line = re.sub("[^一-鿋々ぁ-ゖァ-ヺヲ-゚ー0-90-9a-zA-Za-zA-Z+×÷]", " ", line)
# 先頭の半角空白と全角空白の連続を除去
line = re.sub("^[ ]+", "", line)
# 末尾の半角空白と全角空白の連続を除去
line = re.sub("[ ]+$", "", line)
# 半角空白と全角空白の連続を全角空白に置換
line = re.sub("[ ]{2,}", " ", line)
if line == "":
continue
# 全角空白の連続の場合
if re.match("^ +$", line):
continue
subprocess.call("echo '" + line + "' | open_jtalk -x /var/lib/mecab/dic/open-jtalk/naist-jdic -m '" + voice_file_path + "' -ow '" + os.path.join(temp_dir.name, "temp" + str(wav_count + 1) + ".wav") + "' -r 0.8", shell=True)
wav_count = wav_count + 1
if wav_count <= 0:
temp_dir.cleanup()
message_string_var.set("読み上げ対象と成る有効な文字が無かったので保存しませんでした。")
return
if wav_count == 1:
audioSegment = AudioSegment.from_file(os.path.join(temp_dir.name, "temp1.wav"), format="wav")
audioSegment.export(os.path.join(save_dir, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.mp3")), format="mp3")
temp_dir.cleanup()
message_string_var.set("保存しました" + os.path.join(save_dir, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.mp3")))
return
audioSegment = AudioSegment.from_file(os.path.join(temp_dir.name, "temp1.wav"), format="wav")
for number in range(2, wav_count + 1):
audioSegment = audioSegment + AudioSegment.from_file(os.path.join(temp_dir.name, "temp" + str(number) + ".wav"), format="wav")
audioSegment.export(os.path.join(save_dir, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.mp3")), format="mp3")
temp_dir.cleanup()
message_string_var.set("保存しました" + os.path.join(save_dir, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.mp3")))
save_button = tkinter.Button(root, text="保存", command=save)
save_button.place(x=10, y=640)
root.mainloop()
――――――――――――――――――――
新規登録で充実の読書を
- マイページ
- 読書の状況から作品を自動で分類して簡単に管理できる
- 小説の未読話数がひと目でわかり前回の続きから読める
- フォローしたユーザーの活動を追える
- 通知
- 小説の更新や作者の新作の情報を受け取れる
- 閲覧履歴
- 以前読んだ小説が一覧で見つけやすい
アカウントをお持ちの方はログイン
ビューワー設定
文字サイズ
背景色
フォント
組み方向
機能をオンにすると、画面の下部をタップする度に自動的にスクロールして読み進められます。
応援すると応援コメントも書けます