Python实现聊天机器人(语音录入与语音输出)

本站以下代码,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客户端发送的信息中的命令,并绑定函数

  server.py

  

  2. 训练chatbot的TrainChat.py 主要用来训练chatbot,数据保存在本地sqlite数据库(如果没有数据库自动创建),个人学习此数据足以,作为企业可改为mongodb保存数据,速度会有保障

  TrainChat.py

 

  提供了两种语料训练方法,当然选择自定义语料训练比较好,自定义语料格式,首先我们找到安装chatbot后默认提供的中文语料格式D:\Python\Lib\site-packages\chatterbot_corpus\data\chinese,打开后格式就有了,这里我们按照格式新增一个mytrain文件夹,写入自己的语料文件,如我写的phone.yml

  phone.yml

    Pycham快捷键Ctrl+Shift+F10运行TrainChat.py(如果语料有更新,再运行一次即可) ,此时我们定义的语料已经保存在本地数据库db.sqlite3(没有则自动创建)中了,接下来直接使用即可

 

  3. 录音并保存文件recorder.py,提供录音功能并将录音文件保存在本地

  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识别没用好,不知道是说的语音质量问题,还是配置的传参问题,读者可自行研究,另博主业余爱好可能文章不足之处较多,欢迎指正!

  转载请指明出处!

分类:   Python

猜你喜欢

转载自blog.csdn.net/golddaniu/article/details/80507207
今日推荐