本文共 9813 字,大约阅读时间需要 32 分钟。
这几天看了点PyQt相关的知识,感觉可以结合之前得一些内容做出点什么好玩的东西。
之前做过文本转语音的聊天机器人,昨天又恰好做了关于音频处理的。借此机会,整合一下,来做个有界面的语音文本聊天机器人好了。
先来看看最终的效果图。
对这些基础内容不是很了解的可以参考我之前的文章。
聊天机器人
文本转语音:
音频处理:
环境搭建是个坑,之前一直在用的pyttsx语音引擎竟然不支持Python36,只能在Python27版本使用。所以无奈只能选用微软的提供的win32com.client了。
本机环境如下
所需包一开始我是手动统计的,但是后来觉得版本这块最好还是精确一下,于是使用了pip的一个freeze命令。
pip freeze > requirements.txt
得到了下面的这些所需的库文件(我删除了一些没用到的)。
PyAudio==0.2.11PyQt5==5.8.2pyttsx==1.1pywin32==221requests==2.13.0sip==4.19.2
下面开始针对各大模块简要的介绍一下。
百度语音接口是用来处理本地音频到文本内容转换而使用的。需要用到标准库中的wave库,来处理.wav音频文件。
# coding: utf8# @Author: 郭 璞# @File: baiduyuyin.py # @Time: 2017/5/11 # @Contact: 1064319632@qq.com# @blog: http://blog.csdn.net/marksinoberg# @Description: 借助百度语音识别接口实现本地语音解析import pyaudioimport waveimport requestsimport jsonclass BaiDuYuYin(object): def __init__(self): # get the token self.token = self.gettoken() def gettoken(self): try: apiKey = "Ll0c嘿嘿2ZSGAU" secretKey = "44c8a这个不能说34936227d4a19dc2" auth_url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=" + apiKey + "&client_secret=" + secretKey response = requests.get(url=auth_url) jsondata = response.text return json.loads(jsondata)['access_token'] except Exception as e: raise Exception("Cannot get the token, the reason is {}".format(e)) def parse(self, wavefile='local.wav'): """ 返回音频文件对应的文本内容。 注意返回的是列表类型的数据,待会处理的时候要格外的小心。 :param wavefile: :return: """ try: fp = wave.open(wavefile, 'rb') # 已经录好音的音频片段内容 nframes = fp.getnframes() filelength = nframes * 2 audiodata = fp.readframes(nframes) # 百度语音接口的产品ID cuid = '7519663' server_url = 'http://vop.baidu.com/server_api' + '?cuid={}&token={}'.format(cuid, self.token) headers = { 'Content-Type': 'audio/pcm; rete=8000', 'Content-Length': '{}'.format(filelength), } response = requests.post(url=server_url, headers=headers, data=audiodata) print(response.text) data = json.loads(response.text) if data['err_msg'] == 'success.': return data['result'] else: return '你说的啥啊,听不清听不清!' except Exception as e: raise Exception("Parsing wave file failed. The reason is {}".format(e))if __name__ == '__main__': yuyinclient = BaiDuYuYin() result = yuyinclient.parse(wavefile='local.wav') print(result)
然后是图灵机器人接口,这个用于处理文本对话。免费版其实已经够用了。有需要的自己去申请吧。
# coding: utf8# @Author: 郭 璞# @File: turing.py# @Time: 2017/5/11 # @Contact: 1064319632@qq.com# @blog: http://blog.csdn.net/marksinoberg# @Description: 文字对话接口实现import requestsimport jsonclass TuringRobot(object): def __init__(self): self.apikey = '2a220b3哟哟哟b74c54' self.userid = '产品ID不能说' self.url = 'http://www.tuling123.com/openapi/api' def talk(self, text): payload = { 'key': self.apikey, 'userid': self.userid, 'info': text } response = requests.post(url=self.url, data=payload) return json.loads(response.text)['text']if __name__ == '__main__': turing = TuringRobot() answer = turing.talk('你好吗,我是小黄鸡!') print(answer)
昨天对于音频处理这块做了一点点的研究,今天还是那个套路。默认录音五秒,保存为同一级目录下的local.wav文件。
# coding: utf8# @Author: 郭 璞# @File: recorder.py # @Time: 2017/5/11 # @Contact: 1064319632@qq.com# @blog: http://blog.csdn.net/marksinoberg# @Description: 记录本地录音,默认保存为local.wav, 留作解析引擎备用。import pyaudioimport waveclass Recorder(object): def __init__(self): self.CHUNK = 1024 self.FORMAT = pyaudio.paInt16 self.CHANNELS = 2 self.RATE = 44100 self.RECORD_SECONDS = 5 self.WAVE_OUTPUT_FILENAME = 'local.wav' self.engine = pyaudio.PyAudio() def record(self): try: # 提示语句可以使用一下语音方式,这里先打印算了。 print("Begin Recoding ...") stream = self.engine.open(format=self.FORMAT, channels=self.CHANNELS, rate=self.RATE, input=True, frames_per_buffer=self.CHUNK) # 记录到的音频总数据帧 frames = [] for i in range(0, int(self.RATE / self.CHUNK * self.RECORD_SECONDS)): data = stream.read(self.CHUNK) frames.append(data) # 音频记录完毕 print('Recording Over!') # 释放资源,接触阻塞监听。 stream.stop_stream() stream.close() self.engine.terminate() # 并将音频数据保存到本地音频文件中 wf = wave.open(self.WAVE_OUTPUT_FILENAME, 'wb') wf.setnchannels(self.CHANNELS) wf.setsampwidth(self.engine.get_sample_size(self.FORMAT)) wf.setframerate(self.RATE) wf.writeframes(b''.join(frames)) wf.close() except Exception as e: raise Exception("Recording failed. The reason is {}".format(e))if __name__ == '__main__': recorder = Recorder() recorder.record()
本地语音朗读相当于是一个加分项,之前一直在用的pyttsx这下尴尬了,无奈只能试用第二个方式,不过使用pyttsx的代码我还是留出来吧。万一哪天它支持了Python36,就有更多可选项的丰富功能了。
# coding: utf8# @Author: 郭 璞# @File: localvoicer.py # @Time: 2017/5/11 # @Contact: 1064319632@qq.com# @blog: http://blog.csdn.net/marksinoberg# @Description: 本地语音朗读实现。import win32com.clientclass Reader(object): """ 尴尬的是pyttsx不支持Python36,要不然还可以有更多可选项。 """ def __init__(self): import pyttsx self.engine = pyttsx.init() # optional property self.rate = self.engine.getProperty('rate') self.voices = self.engine.getProperty('voices') self.volume = self.engine.getProperty('volume') def read(self, text="", rate=200, voices="", volume=""): self.engine.say(text) self.engine.runAndWait()class Speaker(object): def __init__(self): self.engine = win32com.client.Dispatch("SAPI.SpVoice") def speak(self, text): self.engine.Speak(text)if __name__ == '__main__': # reader = Reader() # reader.read(text='Hello World!') speaker = Speaker() speaker.speak("hello world! 你好世界")
做完了前面的部分,就差界面了。测试完毕之后发现,各大模块均能正常工作,虽然音频解析那块特别地依赖于网速,校园网这网速我也是醉了。
下面简单的写个界面来“打包美化”一下吧。
# coding: utf8# @Author: 郭 璞# @File: audioui.py # @Time: 2017/5/11 # @Contact: 1064319632@qq.com# @blog: http://blog.csdn.net/marksinoberg# @Description: 外部界面from PyQt5 import QtCore, QtGui, QtWidgetsfrom audiorobot.dispatcher import Dispatcherfrom audiorobot.baiduyuyin import BaiDuYuYinfrom audiorobot.turing import TuringRobotfrom audiorobot.localvoicer import Speakerclass ClientUI(QtWidgets.QWidget): def __init__(self): super(ClientUI, self).__init__() self.dispatcher = Dispatcher() self.baiduyuyin = BaiDuYuYin() self.turingrobot = TuringRobot() self.speaker = Speaker() self.initui() def initui(self): self.setWindowTitle("图灵·聊天室") self.setGeometry(20, 20, 400, 500) # 顶部布局 toplayout = QtWidgets.QHBoxLayout() self.textarea = QtWidgets.QTextBrowser() toplayout.addWidget(self.textarea) # 中间布局 centerlayut = QtWidgets.QHBoxLayout() self.editline = QtWidgets.QLineEdit() self.voicebutton = QtWidgets.QPushButton("发语音") self.textbutton = QtWidgets.QPushButton("发文字") centerlayut.addWidget(self.editline) centerlayut.addWidget(self.voicebutton) centerlayut.addWidget(self.textbutton) mainlayout = QtWidgets.QVBoxLayout() mainlayout.addLayout(toplayout) mainlayout.addLayout(centerlayut) self.setLayout(mainlayout) # 关于事件处理,交给handler来处理即可 self.eventhandler() def eventhandler(self): self.voicebutton.clicked.connect(self.pushvoice) self.textbutton.clicked.connect(self.pushtext) def pushvoice(self): print('voice') # 先保存到本地,再调用语音接口上传 self.dispatcher.record() response = self.baiduyuyin.parse() print('百度语音接口解析到的数据为:{}'.format(response)) self.speaker.speak(text=response) # 更新一下窗体文本域的内容 text = self.textarea.toPlainText()+"\n"+"<<< "+"上传音频中..." self.textarea.setText(text) text = text +"\n>>> " +response self.textarea.setText(text) def pushtext(self): inputtext = self.editline.text() print(inputtext) trans = self.turingrobot.talk(text=inputtext) self.speaker.speak(text=trans) # 更新文本域内容 text = self.textarea.toPlainText() + "\n<<<"+inputtext self.textarea.setText(text) text = self.textarea.toPlainText() + "\n>>> " + trans self.textarea.setText(text) self.editline.clear()if __name__ == '__main__': import sys app = QtWidgets.QApplication(sys.argv) ui = ClientUI() ui.show() sys.exit(app.exec_())
好了,大功告成。运行代码的时候仅仅需要下面的这个命令就可以了。
Python audioui.py
先来看看对于文本的处理,这其实跟之前的聊天机器人没啥区别。仅仅是带了语音朗读罢了。
然后是语音测试,我本人在图书馆。所以为了不打扰别人,录音的时候只能假装咳嗽来掩盖测试录音的事实,所以效果不是很好。但是如果是标准的普通话,测试的结果还是差强人意的。
最后还是来总结一下。
本次也算是整合的比较多的内容了。模块内测试都是用的
if __name__ == "__main__": # testing code pass
看起来还算不错,单元测试倒是没什么必要,毕竟代码量还很少。集成测试也算是马马虎虎,功能这块倒是还能满足需求,但是以后如果代码量大了的话,还是要好好测测的,以防万一。
已知的缺点就是界面这块。录音的时候要是能加一个statusBar实时提醒录音进度就好了,而且录音的时候是阻塞线程的,不是很优雅。