文語合成是 pzh-py-speech 的核心功能,pzh-py-speech 借助的是 pyttsx3 以及 eSpeak 引擎來實(shí)現(xiàn)的文語合成功能,今天痞子衡為大家介紹文語合成在 pzh-py-speech 中是如何實(shí)現(xiàn)的。
一、pyttsx3 簡(jiǎn)介
pyttsx3 是一套基于實(shí)現(xiàn) SAPI5 文語合成引擎的 Python 封裝庫(kù),該庫(kù)的設(shè)計(jì)者為 Natesh M Bhat,該庫(kù)其實(shí)是 pyTTS 和 pyttsx 項(xiàng)目的延續(xù),pyttsx3 主要是為 Python3 版本設(shè)計(jì)的,但同時(shí)也兼容 Python2。JaysPySPEECH 使用的是 pyttsx3 2.7。
pyttsx3 系統(tǒng)的官方主頁如下:
pyttsx3 官方主頁: https://github.com/nateshmbhat/pyttsx3
pyttsx3 安裝方法: https://pypi.org/project/pyttsx3/
pyttsx3 的使用足夠簡(jiǎn)單,其官方文檔 https://pyttsx3.readthedocs.io/en/latest/engine.html 半小時(shí)即可讀完,下面是最簡(jiǎn)單的一個(gè)示例代碼:
import pyttsx3;
engine = pyttsx3.init();
engine.say("I will speak this text");
engine.runAndWait() ;
1.1 Microsoft Speech API (SAPI5)引擎
前面痞子衡講了 pyttsx3 基于的文語合成內(nèi)核是 SAPI5 引擎,這是微軟公司開發(fā)的 TTS 引擎,其官方主頁如下:
SAPI5 官方文檔: https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms723627(v%3dvs.85)
由于 pyttsx3 已經(jīng)將 SAPI5 封裝好,所有我們沒有必要關(guān)注 SAPI5 本身的 TTS 實(shí)現(xiàn)原理。
1.2 確認(rèn) PC 支持的語音包
在使用 pyttsx3 進(jìn)行文語合成時(shí),依賴的是當(dāng)前 PC 的語音環(huán)境,打開控制面板(Control Panel)->語言識(shí)別(Speech Recognition),可見到如下頁面:
痞子衡使用的 PC 是 Win10 英文版,故默認(rèn)僅有英文語音包(David 是男聲,Zira 是女聲),這點(diǎn)也可以使用如下 pyttsx3 調(diào)用代碼來確認(rèn):
import pyttsx3;
ttsObj = pyttsx3.init()
voices = ttsObj.getProperty('voices')
for voice in voices:
? ? print ('id = {} nname = {} n'.format(voice.id, voice.name))
代碼運(yùn)行結(jié)果如下:
id = HKEY_LOCAL_MACHINESOFTWAREMicrosoftSpeechVoicesTokensTTS_MS_EN-US_DAVID_11.0
name = Microsoft David Desktop - English (United States)
id = HKEY_LOCAL_MACHINESOFTWAREMicrosoftSpeechVoicesTokensTTS_MS_EN-US_ZIRA_11.0
name = Microsoft Zira Desktop - English (United States)
1.3 為 PC 增加語音包支持
要想在使用 pzh-py-speech 時(shí)可以實(shí)現(xiàn)中英雙語合成,要確保 PC 上既有英文語音包也有中文語音包,痞子衡 PC 上當(dāng)前僅有英文語音包,故需要安裝中文語音包(安裝其他語言語音包的方法類似)。
Windows 系統(tǒng)下中文語音包有很多,可以使用第三方公司提供的語音包(比如 NeoSpeech 公司 ),也可以使用微軟提供的語音包,痞子衡選用的是經(jīng)典的慧慧語音包(zh-CN_HuiHui)。
進(jìn)入 Microsoft Speech Platform - Runtime (Version 11) 和 Microsoft Speech Platform - Runtime Languages (Version 11) 下載頁面將選中文件下載(親測(cè)僅能用 Google Chrome 瀏覽器才能正常訪問,IE 竟然也無法打開):
先安裝 SpeechPlatformRuntime.msi(雙擊安裝即可),安裝完成之后重啟電腦,再安裝 MSSpeech_TTS_zh-CN_HuiHui.msi,安裝結(jié)束之后需要修改注冊(cè)表,打開 Run(Win 鍵+R 鍵)輸入"regedit"即可看到如下 registry 編輯界面,HKEY_LOCAL_MACHINESOFTWAREMicrosoftSpeechVoices 路徑下可以看到默認(rèn)語音包(DAVID, ZIRA),HKEY_LOCAL_MACHINESOFTWAREMicrosoftSpeech Serverv11.0Voices 路徑下可看到新安裝的語音包(HuiHui):
右鍵 HKEY_LOCAL_MACHINESOFTWAREMicrosoftSpeech Serverv11.0Voices,將其導(dǎo)出成 .reg 文件,使用文本編輯器打開這個(gè) .reg 文件將其中"Speech Serverv11.0"全部替換成"Speech"并保存,然后將這個(gè)修改后的 .reg 文件再導(dǎo)入注冊(cè)表。
導(dǎo)入成功后,便可在注冊(cè)表和語音識(shí)別選項(xiàng)里看到 Huihui 身影:
Note: 上述修改僅針對(duì) 32bit 操作系統(tǒng),如果是 64bit 系統(tǒng),需要同時(shí)將
HKEY_LOCAL_MACHINESOFTWAREWOW6432NodeMicrosoftSpeech Serverv11.0Voices 路徑的注冊(cè)表按同樣方法也操作一遍。
二、eSpeak 簡(jiǎn)介
由于 pyttsx3 僅能在線發(fā)聲,無法將合成后的語音保存為 wav 文件,因此痞子衡需要為 JaysPySPEECH 再尋一款可以保存為 wav 的 TTS 引擎。痞子衡選中的是 eSpeak,eSpeak 是一個(gè)簡(jiǎn)潔的開源語音合成軟件,用 C 語言寫成,支持英語和其他很多語言,同時(shí)也支持 SAPI5 接口,合成的語音可以導(dǎo)出為 wav 文件。
eSpeak 的官方主頁如下:
eSpeak 官方主頁: http://espeak.sourceforge.net/
eSpeak 下載安裝: http://espeak.sourceforge.net/download.html
eSpeak 補(bǔ)充語言包: http://espeak.sourceforge.net/data/index.html
eSpeak 從標(biāo)準(zhǔn)輸入或者輸入文件中讀取文本,雖然語音輸出與真人聲音相去甚遠(yuǎn),但是在項(xiàng)目需要的時(shí)候,eSpeak 仍不失為一個(gè)簡(jiǎn)便快捷的工具。
痞子衡將 eSpeak 1.48.04 安裝在了 C:tools_mcueSpeak 路徑下,進(jìn)入這個(gè)路徑可以找到 eSpeakcommand_lineespeak.exe,這便是我們需要調(diào)用的工具,為了方便調(diào)用,你需要將"C:tools_mcueSpeakcommand_line"路徑加入系統(tǒng)環(huán)境變量 Path 中。
關(guān)于中文支持,在 eSpeakespeak-datazh_dict 文件里已經(jīng)包含了基本的中文字符,但是如要想要完整的中文支持,還需要下載 zh_listx.zip 中文語音包,解壓后將里面的 zh_listx 文件放到 eSpeakdictsource 目錄下,并且在 eSpeakdictsource 路徑下執(zhí)行命令"espeak --compile=zh",執(zhí)行成功后可以看到 eSpeakespeak-datazh_dict 文件明顯變大了。
eSpeak 對(duì)于 python 來說是個(gè)外部程序,我們需要借助 subprocess 來調(diào)用 espeak.exe,下面是示例代碼:
import subprocess
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
enText = "Hello world"
zhText = u"你好世界"
txtFile = "C:/test.txt" ?#文件內(nèi)為中文
wavFile = "C:/test.wav"
# 在線發(fā)音(-v 是設(shè)置 voice,en 是英文,m3 男聲,zh 是中文,f3 是女聲)
subprocess.call(["espeak", "-ven+m3", enText])
subprocess.call(["espeak", "-vzh+f3", zhText])
# 保存為 wav 文件(第一種方法僅能保存英文 wav,如果想保存其他語言 wav 需要使用第二種方法)
subprocess.call(["espeak","-w"+wavFile, enText])
subprocess.call(["espeak","-vzh+f3", "-f"+txtFile, "-w"+wavFile])
如果想直接體驗(yàn) eSpeak 的發(fā)音質(zhì)量,可以直接打開 eSpeakTTSApp.exe 應(yīng)用程序,軟件使用非常簡(jiǎn)單:
三、pzh-py-speech 文語合成實(shí)現(xiàn)
文語合成實(shí)現(xiàn)主要分為兩部分:TTS, TTW。實(shí)現(xiàn) TTS 需要 import pyttsx3,實(shí)現(xiàn) TTW 需要借助 subprocess 調(diào)用 eSpeak,下面 痞子衡分別介紹這兩部分的實(shí)現(xiàn):
3.1 Text-to-Speech 實(shí)現(xiàn)
TTS 代碼實(shí)現(xiàn)其實(shí)很簡(jiǎn)單,目前僅實(shí)現(xiàn)了 pyttsx3 引擎,并且僅支持中英雙語識(shí)別。具體到 pzh-py-speech 上主要是實(shí)現(xiàn) GUI 界面上"TTS"按鈕的回調(diào)函數(shù),即 textToSpeech(),如果用戶選定了配置參數(shù)(語言類型、發(fā)音人類型、TTS 引擎類型),并點(diǎn)擊了"TTS"按鈕,此時(shí)便會(huì)觸發(fā) textToSpeech()的執(zhí)行。代碼如下:
reload(sys)
sys.setdefaultencoding('utf-8')
import pyttsx3
class mainWin(win.speech_win):
? ? def __init__(self, parent):
? ? ? ? # ...
? ? ? ? self.ttsObj = None
? ? def refreshVoice( self, event ):
? ? ? ? languageType, languageName = self.getLanguageSelection()
? ? ? ? engineType = self.m_choice_ttsEngine.GetString(self.m_choice_ttsEngine.GetSelection())
? ? ? ? if engineType == 'pyttsx3 - SAPI5':
? ? ? ? ? ? if self.ttsObj == None:
? ? ? ? ? ? ? ? ?self.ttsObj = pyttsx3.init()
? ? ? ? ? ? voices = self.ttsObj.getProperty('voices')
? ? ? ? ? ? voiceItems = [None] * len(voices)
? ? ? ? ? ? itemIndex = 0
? ? ? ? ? ? for voice in voices:
? ? ? ? ? ? ? ? voiceId = voice.id.lower()
? ? ? ? ? ? ? ? voiceName = voice.name.lower()
? ? ? ? ? ? ? ? if (voiceId.find(languageType.lower()) != -1) or (voiceName.find(languageName.lower()) != -1):
? ? ? ? ? ? ? ? ? ? voiceItems[itemIndex] = voice.name
? ? ? ? ? ? ? ? ? ? itemIndex += 1
? ? ? ? ? ? voiceItems = voiceItems[0:itemIndex]
? ? ? ? ? ? self.m_choice_voice.Clear()
? ? ? ? ? ? self.m_choice_voice.SetItems(voiceItems)
? ? ? ? else:
? ? ? ? ? ? voiceItem = ['N/A']
? ? ? ? ? ? self.m_choice_voice.Clear()
? ? ? ? ? ? self.m_choice_voice.SetItems(voiceItem)
? ? def textToSpeech( self, event ):
? ? ? ?
# 獲取語音語言類型(English/Chinese)
? ? ? ?
languageType, languageName = self.getLanguageSelection()
? ? ? ? # 從 asrttsText 文本框獲取要轉(zhuǎn)換的文本
? ? ? ? lines = self.m_textCtrl_asrttsText.GetNumberOfLines()
? ? ? ? if lines != 0:
? ? ? ? ? ? data = ''
? ? ? ? ? ? for i in range(0, lines):
? ? ? ? ? ? ? ? data += self.m_textCtrl_asrttsText.GetLineText(i)
? ? ? ? else:
? ? ? ? ? ? return
? ? ? ? ttsEngineType = self.m_choice_ttsEngine.GetString(self.m_choice_ttsEngine.GetSelection())
? ? ? ? if ttsEngineType == 'pyttsx3 - SAPI5':
? ? ? ? ? ?
# 嘗試創(chuàng)建 pyttsx3 文語合成對(duì)象 ttsObj
? ? ? ? ? ?
if self.ttsObj == None:
? ? ? ? ? ? ? ? ?self.ttsObj = pyttsx3.init()
? ? ? ? ? ?
# 搜索當(dāng)前 PC 是否存在指定語言類型的發(fā)聲人
? ? ? ? ? ?
hasVoice = False
? ? ? ? ? ? voices = self.ttsObj.getProperty('voices')
? ? ? ? ? ? voiceSel = self.m_choice_voice.GetString(self.m_choice_voice.GetSelection())
? ? ? ? ? ? for voice in voices:
? ? ? ? ? ? ? ? #print ('id = {} nname = {} nlanguages = {} n'.format(voice.id, voice.name, voice.languages))
? ? ? ? ? ? ? ? voiceId = voice.id.lower()
? ? ? ? ? ? ? ? voiceName = voice.name.lower()
? ? ? ? ? ? ? ? if (voiceId.find(languageType.lower()) != -1) or (voiceName.find(languageName.lower()) != -1):
? ? ? ? ? ? ? ? ? ? if (voiceSel == '') or (voiceSel == voice.name):
? ? ? ? ? ? ? ? ? ? ? ? hasVoice = True
? ? ? ? ? ? ? ? ? ? ? ? break
? ? ? ? ? ? if hasVoice:
? ? ? ? ? ? ? ?
# 調(diào)用 pyttsx3 里的 say()和 runAndWait()完成文語合成,直接在線發(fā)音
? ? ? ? ? ? ? ?
self.ttsObj.setProperty('voice', voice.id)
? ? ? ? ? ? ? ? self.ttsObj.say(data)
? ? ? ? ? ? ? ? self.statusBar.SetStatusText("TTS Conversation Info: Run and Wait")
? ? ? ? ? ? ? ? self.ttsObj.runAndWait()
? ? ? ? ? ? ? ? self.statusBar.SetStatusText("TTS Conversation Info: Successfully")
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? self.statusBar.SetStatusText("TTS Conversation Info: Language is not supported by current PC")
? ? ? ? ? ? self.textToWav(data, languageType)
? ? ? ? else:
? ? ? ? ? ? self.statusBar.SetStatusText("TTS Conversation Info: Unavailable TTS Engine")
3.2 Text-to-Wav 實(shí)現(xiàn)
TTW 代碼實(shí)現(xiàn)也很簡(jiǎn)單,目前僅實(shí)現(xiàn)了 eSpeak 引擎,并且僅支持中英雙語識(shí)別。具體到 pzh-py-speech 上主要是實(shí)現(xiàn) GUI 界面上"TTW"按鈕的回調(diào)函數(shù),即 textToWav(),如果用戶選定了配置參數(shù)(發(fā)音人性別類型、TTW 引擎類型),并點(diǎn)擊了"TTW"按鈕,此時(shí)便會(huì)觸發(fā) textToWav()的執(zhí)行。代碼如下:
import subprocess
class mainWin(win.speech_win):
? ? def textToWav(self, text, language):
? ? ? ? fileName = self.m_textCtrl_ttsFileName.GetLineText(0)
? ? ? ? if fileName == '':
? ? ? ? ? ? fileName = 'tts_untitled1.wav'
? ? ? ? ttsFilePath = os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))), 'conv', 'tts', fileName)
? ? ? ? ttwEngineType = self.m_choice_ttwEngine.GetString(self.m_choice_ttwEngine.GetSelection())
? ? ? ? if ttwEngineType == 'eSpeak TTS':
? ? ? ? ? ? ttsTextFile = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'ttsTextTemp.txt')
? ? ? ? ? ? ttsTextFileObj = open(ttsTextFile, 'wb')
? ? ? ? ? ? ttsTextFileObj.write(text)
? ? ? ? ? ? ttsTextFileObj.close()
? ? ? ? ? ? try:
? ? ? ? ? ? ? ? #espeak_path = "C:/tools_mcu/eSpeak/command_line/espeak.exe"
? ? ? ? ? ? ? ? #subprocess.call([espeak_path, "-v"+languageType[0:2], text])
? ? ? ? ? ? ? ? gender = self.m_choice_gender.GetString(self.m_choice_gender.GetSelection())
? ? ? ? ? ? ? ? gender = gender.lower()[0] + '3'
? ? ? ? ? ? ? ?
# 調(diào)用 espeak.exe 完成文字到 wav 文件的轉(zhuǎn)換
? ? ? ? ? ? ? ? subprocess.call(["espeak", "-v"+language[0:2]+'+'+gender, "-f"+ttsTextFile, "-w"+ttsFilePath])
? ? ? ? ? ? except:
? ? ? ? ? ? ? ? self.statusBar.SetStatusText("TTW Conversation Info: eSpeak is not installed or its path is not added into system environment")
? ? ? ? ? ? os.remove(ttsTextFile)
? ? ? ? else:
? ? ? ? ? ? self.statusBar.SetStatusText("TTW Conversation Info: Unavailable TTW Engine")
至此,語音處理工具 pzh-py-speech 誕生之文語合成實(shí)現(xiàn)痞子衡便介紹完畢了,掌聲在哪里~~~