本站以下代码,Python3.6完美运行,使用工具Pycham,系统Win10
一、思路
1. 功能简述
登录后进入聊天界面,如果服务器都在同一个地址,则都进入同一个房间,进入离开发消息都可以看到,输入“tuling”或“chatbot”可以切换为和Tuling机器人或者ChatBot聊天,如下图
Tuling,是图灵,已经录入大量中文对话,直接调用接口即可实现自动回复,实用于开发聊天软件
ChatBot,可自行训练机器人,让机器人拥有自己的语料库,实用于企业智能聊天个性化
2. 需要的核心技术
a. 输入语音经识别后转换为输入文字
b. Tuling或ChatBot两种聊天模式获取输出文字
c. 输出文字转换为输出语音并播放
以上a和c主要调用Baidu提供的API进行转换,如果理解本文,可自行尝试调用Google提供的API实现,Google技术强大但是否对中文支持良好,博主不存尝试不妄自揣度
二、源码设计(贴上完整源码自己去理解)
运行顺序,TrainChat.py训练本地机器人 —— server.py开启服务器 —— n次client.py运行无限次,每次运行都可登陆一个用户
1. 服务器server.py 主要处理用户的登录校验,房间的人员消息处理,此处通过config.py中配置的列表PORT = range(1, 3)生成两个房间(实用时可以无限个),地址分别是127.0.0.1:1和127.0.0.1:2,启用客户端前,这个服务器要先运行
此中CommandHandler类拆解client客户端发送的信息中的命令,并绑定函数
2. 训练chatbot的TrainChat.py 主要用来训练chatbot,数据保存在本地sqlite数据库(如果没有数据库自动创建),个人学习此数据足以,作为企业可改为mongodb保存数据,速度会有保障
提供了两种语料训练方法,当然选择自定义语料训练比较好,自定义语料格式,首先我们找到安装chatbot后默认提供的中文语料格式D:\Python\Lib\site-packages\chatterbot_corpus\data\chinese,打开后格式就有了,这里我们按照格式新增一个mytrain文件夹,写入自己的语料文件,如我写的phone.yml
Pycham快捷键Ctrl+Shift+F10运行TrainChat.py(如果语料有更新,再运行一次即可) ,此时我们定义的语料已经保存在本地数据库db.sqlite3(没有则自动创建)中了,接下来直接使用即可
3. 录音并保存文件recorder.py,提供录音功能并将录音文件保存在本地
4. chatbot.py, 提供播放音频文件,调用图灵Tuling接口返回文本信息,调用chatbot返回文本信息,调用百度api语音识别,调用百度api转文本为语音(有两个百度api都可用,第一个不用密匙),其中chatbot的数据库配置要和TrainChat.py中配置的名称一致
1 import pygame 2 from chatterbot import ChatBot 3 import requests 4 import json 5 from config import * 6 import time 7 import os 8 import random 9 import urllib.request 10 import base64 11 12 13 # 初始化百度返回的音频文件地址,后面会变为全局变量,随需改变 14 mp3_url = 'E:\Python_Doc\\voice_du\\voice_ss.mp3' 15 16 17 # 播放Mp3文件 18 def play_mp3(): 19 # 接受服务器的消息 20 pygame.mixer.init() 21 pygame.mixer.music.load(mp3_url) 22 pygame.mixer.music.play() 23 while pygame.mixer.music.get_busy(): 24 time.sleep(1) 25 pygame.mixer.music.stop() 26 pygame.mixer.quit() 27 28 29 # 删除声音文件 30 def remove_voice(): 31 path = r"E:\Python_Doc\voice_du" 32 for i in os.listdir(path): 33 path_file = os.path.join(path, i) 34 try: 35 os.remove(path_file) 36 except: 37 continue 38 39 40 # 图灵自动回复 41 def tuling(info): 42 url = tuling_url + "?key=%s&info=%s" % (tuling_app_key, info) 43 content = requests.get(url, headers=headers) 44 answer = json.loads(content.text) 45 return answer['text'] 46 47 48 # 聊天机器人回复 49 def chatbot(info): 50 my_bot = ChatBot("", read_only=True, 51 database="./db.sqlite3") 52 res = my_bot.get_response(info) 53 return str(res) 54 55 56 # 百度讲文本转为声音文件保存在本地 tts地址,无需token实时认证 57 def baidu_api(answer): 58 api_url = '{11}?idx={0}&tex={1}&cuid={2}&cod={3}&lan={4}&ctp={5}&pdt={6}&spd={7}&per={8}&vol={9}&pit={10}'\ 59 .format(baidu_api_set["idx"], answer, baidu_api_set["cuid"], baidu_api_set["cod"], baidu_api_set["lan"], 60 baidu_api_set["ctp"], baidu_api_set["pdt"], baidu_api_set["spd"], baidu_api_set["per"], 61 baidu_api_set["vol"], baidu_api_set["pit"], baidu_api_url) 62 res = requests.get(api_url, headers=headers2) 63 # 本地Mp3语音文件保存位置 64 iname = random.randrange(1, 99999) 65 global mp3_url 66 mp3_url = 'E:\Python_Doc\\voices\\voice_tts' + str(iname) + '.mp3' 67 with open(mp3_url, 'wb') as f: 68 f.write(res.content) 69 70 71 # 百度讲文本转为声音文件保存在本地 方法2 tsn地址 72 def baidu_api2(answer): 73 # 获取access_token 74 token = getToken() 75 get_url = baidu_api_url2 % (urllib.parse.quote(answer), "test", token) 76 voice_data = urllib.request.urlopen(get_url).read() 77 # 本地Mp3语音文件保存位置 78 name = random.randrange(1, 99999) 79 global mp3_url 80 mp3_url = 'E:\Python_Doc\\voice_du\\voice_tsn' + str(name) + '.mp3' 81 voice_fp = open(mp3_url, 'wb+') 82 voice_fp.write(voice_data) 83 voice_fp.close() 84 return 85 86 87 # 百度语音转文本 88 def getText(filename): 89 # 获取access_token 90 token = getToken() 91 data = {} 92 data['format'] = 'wav' 93 data['rate'] = 16000 94 data['channel'] = 1 95 data['cuid'] = str(random.randrange(123456, 999999)) 96 data['token'] = token 97 wav_fp = open(filename, 'rb') 98 voice_data = wav_fp.read() 99 data['len'] = len(voice_data) 100 data['speech'] = base64.b64encode(voice_data).decode('utf-8') 101 post_data = json.dumps(data) 102 # 语音识别的api url 103 upvoice_url = 'http://vop.baidu.com/server_api' 104 r_data = urllib.request.urlopen(upvoice_url, data=bytes(post_data, encoding="utf-8")).read() 105 print(json.loads(r_data)) 106 err = json.loads(r_data)['err_no'] 107 if err == 0: 108 return json.loads(r_data)['result'][0] 109 else: 110 return json.loads(r_data)['err_msg'] 111 112 113 # 获取百度API调用的认证,实时生成,因为有时间限制 114 def getToken(): 115 # token认证的url 116 api_url = "https://openapi.baidu.com/oauth/2.0/token?" \ 117 "grant_type=client_credentials&client_id=%s&client_secret=%s" 118 token_url = api_url % (BaiDu_API_Key_GetVoi, BaiDu_Secret_Key_GetVoi) 119 r_str = urllib.request.urlopen(token_url).read() 120 token_data = json.loads(r_str) 121 token_str = token_data['access_token'] 122 return token_str
5. client.py,提供登录窗口,聊天窗口,已及响应事件,say按钮绑定sayDown录音和sayUp获取语音文本并发送两个事件,Users显示当前房间所有用户...
1 import wx 2 import telnetlib 3 from time import sleep 4 import _thread as thread 5 import time 6 import os 7 from chatbot import baidu_api2, chatbot, tuling, play_mp3, remove_voice, getText 8 from config import BOTS, BOT, default_server, VOICE_SWITCH 9 from recorder import recording 10 11 12 bot_use = BOT 13 14 15 class LoginFrame(wx.Frame): 16 """ 17 登录窗口 18 """ 19 def __init__(self, parent, id, title, size): 20 # 初始化,添加控件并绑定事件 21 wx.Frame.__init__(self, parent, id, title) 22 self.SetSize(size) 23 self.Center() 24 self.serverAddressLabel = wx.StaticText(self, label="Server Address", pos=(15, 40), size=(120, 25)) 25 self.userNameLabel = wx.StaticText(self, label="UserName", pos=(45, 90), size=(120, 25)) 26 self.serverAddress = wx.TextCtrl(self, value=default_server, 27 pos=(120, 37), size=(150, 25), style=wx.TE_PROCESS_ENTER) 28 self.userName = wx.TextCtrl(self, pos=(120, 87), size=(150, 25), style=wx.TE_PROCESS_ENTER) 29 self.loginButton = wx.Button(self, label='Login', pos=(50, 145), size=(90, 30)) 30 self.exitButton = wx.Button(self, label='Exit', pos=(180, 145), size=(90, 30)) 31 # 绑定登录方法 32 self.loginButton.Bind(wx.EVT_BUTTON, self.login) 33 # 绑定退出方法 34 self.exitButton.Bind(wx.EVT_BUTTON, self.exit) 35 # 服务器输入框Tab事件 36 self.serverAddress.SetFocus() 37 self.Bind(wx.EVT_TEXT_ENTER, self.usn_focus, self.serverAddress) 38 # 用户名回车登录 39 self.Bind(wx.EVT_TEXT_ENTER, self.login, self.userName) 40 self.Show() 41 42 # 回车调到用户名输入栏 43 def usn_focus(self, event): 44 self.userName.SetFocus() 45 46 def login(self, event): 47 # 登录处理 48 try: 49 serverAddress = self.serverAddress.GetLineText(0).split(':') 50 con.open(serverAddress[0], port=int(serverAddress[1]), timeout=10) 51 response = con.read_some() 52 if response != b'Connect Success': 53 self.showDialog('Error', 'Connect Fail!', (200, 100)) 54 return 55 con.write(('login ' + str(self.userName.GetLineText(0)) + '\n').encode("utf-8")) 56 response = con.read_some() 57 if response == b'UserName Empty': 58 self.showDialog('Error', 'UserName Empty!', (200, 100)) 59 elif response == b'UserName Exist': 60 self.showDialog('Error', 'UserName Exist!', (200, 100)) 61 else: 62 self.Close() 63 ChatFrame(None, 2, title='当前用户:'+str(self.userName.GetLineText(0)), size=(515, 400)) 64 except Exception: 65 self.showDialog('Error', 'Connect Fail!', (95, 20)) 66 67 def exit(self, event): 68 self.Close() 69 70 # 显示错误信息对话框 71 def showDialog(self, title, content, size): 72 dialog = wx.Dialog(self, title=title, size=size) 73 dialog.Center() 74 wx.StaticText(dialog, label=content) 75 dialog.ShowModal() 76 77 78 class ChatFrame(wx.Frame): 79 """ 80 聊天窗口 81 """ 82 def __init__(self, parent, id, title, size): 83 # 初始化,添加控件并绑定事件 84 wx.Frame.__init__(self, parent, id, title, style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | 85 wx.DEFAULT_FRAME_STYLE) 86 self.SetSize(size) 87 self.Center() 88 self.chatFrame = wx.TextCtrl(self, pos=(5, 5), size=(490, 310), style=wx.TE_MULTILINE | wx.TE_READONLY) 89 self.sayButton = wx.Button(self, label="Say", pos=(5, 320), size=(58, 25)) 90 self.message = wx.TextCtrl(self, pos=(65, 320), size=(240, 25), style=wx.TE_PROCESS_ENTER) 91 self.sendButton = wx.Button(self, label="Send", pos=(310, 320), size=(58, 25)) 92 self.usersButton = wx.Button(self, label="Users", pos=(373, 320), size=(58, 25)) 93 self.closeButton = wx.Button(self, label="Close", pos=(436, 320), size=(58, 25)) 94 # 发送按钮绑定发送消息方法 95 self.sendButton.Bind(wx.EVT_BUTTON, self.send) 96 # 输入框回车发送信息 97 self.message.SetFocus() 98 # 发送消息 99 self.sayButton.Bind(wx.EVT_LEFT_DOWN, self.sayDown) 100 self.sayButton.Bind(wx.EVT_LEFT_UP, self.sayUp) 101 # 发送消息 102 self.Bind(wx.EVT_TEXT_ENTER, self.send, self.message) 103 # Users按钮绑定获取在线用户数量方法 104 self.usersButton.Bind(wx.EVT_BUTTON, self.lookUsers) 105 # 关闭按钮绑定关闭方法 106 self.closeButton.Bind(wx.EVT_BUTTON, self.close) 107 thread.start_new_thread(self.receive, ()) 108 # self.ShowFullScreen(True) 109 self.Show() 110 111 def sayDown(self, event): 112 thread.start_new_thread(recording, ()) 113 # print("ON") 114 115 def sayUp(self, event): 116 sayText = getText(r"E:\Python_Doc\voice_say\say_voice.wav") 117 self.message.AppendText(str(sayText)) 118 self.send(self) 119 120 def send(self, event): 121 # 发送消息 122 message = str(self.message.GetLineText(0)).strip() 123 global bot_use 124 if message != '': 125 if message == "chatbot": 126 bot_use = "ChatBot" 127 self.message.Clear() 128 con.write(('noone_say You have been changed ChatBot-Chat' + '\n').encode("utf-8")) 129 return 130 elif message == "tuling": 131 bot_use = "TuLing" 132 self.message.Clear() 133 con.write(('noone_say You have been changed TuLing-Chat' + '\n').encode("utf-8")) 134 return 135 elif message == "user": 136 bot_use = "User" 137 self.message.Clear() 138 con.write(('noone_say You have been changed User-Chat' + '\n').encode("utf-8")) 139 return 140 con.write(('say ' + message + '\n').encode("utf-8")) 141 self.message.Clear() 142 # 机器人回复 143 if bot_use == "ChatBot": 144 answer = chatbot(message) 145 con.write(('chatbot_say ' + answer + '\n').encode("utf-8")) 146 elif bot_use == "TuLing": 147 answer = tuling(message) 148 con.write(('tuling_say ' + answer + '\n').encode("utf-8")) 149 elif bot_use == "User": 150 return 151 152 if VOICE_SWITCH: 153 # 写本地音乐文件 154 baidu_api2(answer) 155 # 新建线程播放音乐 156 thread.start_new_thread(play_mp3, ()) 157 return 158 159 def lookUsers(self, event): 160 # 查看当前在线用户 161 con.write(b'look\n') 162 163 def close(self, event): 164 # 关闭窗口 165 thread.start_new_thread(remove_voice, ()) 166 con.write(b'logout\n') 167 con.close() 168 self.Close() 169 170 def receive(self): 171 # 接受服务器的消息 172 while True: 173 sleep(0.6) 174 result = con.read_very_eager() 175 if result != '': 176 self.chatFrame.AppendText(result) 177 178 179 if __name__ == '__main__': 180 app = wx.App() 181 con = telnetlib.Telnet() 182 LoginFrame(None, -1, title="Login", size=(320, 250)) 183 app.MainLoop()
6. config配置文件,*号内容自行去对应官网申请,本文不提供,文字转换语音并播放开关默认Flase,可自己修改
1 # 默认输入的服务器地址,测试时候使用,避免登录总是输入地址麻烦 2 default_server = "127.0.0.1:1" 3 4 # 定义服务器端口,一个端口一个房间 5 PORT = range(1, 3) 6 7 # 图灵Tuling机器人还是ChatBot聊天机器人选择 8 BOTS = ["TuLing", "ChatBot", "User"] 9 BOT = BOTS[2] 10 11 # 浏览器请求头文件 12 headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 ' 13 '(KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36', } 14 headers2 = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 ' 15 '(KHTML, like Gecko)Chrome/62.0.3202.94 Safari/537.36'} 16 17 # 图灵密匙,自动回复地址,选择的key不同,tuling机器人的回答也各不相同 18 tuling_app_key = "******" 19 tuling_app_key2 = "******" 20 tuling_url = "http://www.tuling123.com/openapi/api" 21 22 # 语音保存播放开关 23 VOICE_SWITCH = False 24 25 # 百度文本转语音地址和配置 tts地址 26 baidu_api_url = "http://tts.baidu.com/text2audio" 27 baidu_api_set = {"idx": 1, "cuid": "baidu_speech_demo", "cod": 2, 28 "lan": "zh", "ctp": 1, "pdt": 1, "spd": 4, "per": 4, "vol": 5, "pit": 5} 29 30 # 百度文字转语音 tsn地址 31 baidu_api_url2 = "http://tsn.baidu.com/text2audio?tex=%s&lan=zh&cuid=%s&ctp=1&tok=%s" 32 BaiDu_API_Key_GetVoi = "******" 33 BaiDu_Secret_Key_GetVoi = "******" 34 35 # 百度语音识别 36 BaiDu_App_ID = "******" 37 BaiDu_API_Key = "******" 38 BaiDu_Secret_Key = "******" 39 BaiDu_OpenApi_Url = "https://openapi.baidu.com/oauth/2.0/token" \ 40 "?grant_type=client_credentials&client_id=%&client_secret=%"
三、总结
此文在语音识别之处,尚有不足,因为对百度提供的api识别没用好,不知道是说的语音质量问题,还是配置的传参问题,读者可自行研究,另博主业余爱好可能文章不足之处较多,欢迎指正!
转载请指明出处!