Bootstrap

python实现类似Siri的AI语音聊天机器人(需要图灵机器人和百度语音合成的API)

程序简介:模仿Siri,参考微信公众号:学习python的正确姿势,在语音播放和搜索的基础上,增加了语音输入和打开常用软件功能。

一、配置

  • Windows10系统
  • python3.7
  • pycharm

(其实这些都不重要)

二、主要体系框架

(1)图灵机器人API

自己做一个生动的能聊天的机器人太麻烦了,不过图灵机器人已经帮我们做好了,而且提供了开放的接口,我们注册一个,使用它的接口就可以轻松实现,链接在下方:

图灵机器人官网

注册登录后,如下图所示创建机器人创建机器人

创建好后你就拥有了这样一个语音聊天机器人:
机器人API

你还可以在“人物设置”栏里设置它的名字,性别,年龄之类的
还挺别致。。
人物设置
这个不重要,我们看看它的API使用文档,了解一下怎么用的
图灵文档
嗯,好像很简单的亚子!
不管了,赶紧来试一下。。

import requests
import json

urls = 'http://openapi.tuling123.com/openapi/api/v2'	# 图灵接口的url
api_key = "!!!这里要用你自己的api密钥!!!"

prologue = "主人您好,我是Niubility,爱你哦~"	# 定义开场白
count = 1	# 用来计数输入的次数

# 回复
def respond(data):
	data_dict = {
      "reqType":0,
        "perception": {
            "inputText": {
                "text": data
            },
        },
        "userInfo": {
            "apiKey": api_key,
            "userId": "584403"
        }
    }
    result = requests.post(urls, json=data_dict)
    content = (result.content).decode('utf-8')
    # print(content)
    ans = json.loads(content)
    text = ans['results'][0]['values']['text']
    print('Niubility:',text)    # 机器人取名就叫Niubility

if __name__ == "__main__":
	print("Niubility:", prologue)
	while True:
		data = input('{}//你:'.format(count))  # 输入对话内容
		respond(data)
		count += 1
    

运行一下:
在这里插入图片描述
阔以阔以!!!
第一步,图灵机器人API就这样解决了,下一步!
(图灵未认证用户一天只能聊三句话,认证之后一天可以聊100句,我们只要学习它的用法就可以了)

(2)百度语音合成API

同样百度提供了开放的语音合成API,我们也注册一个吧,很简单,网址:

百度智能云官网

方法和上面图灵机器人差不多
注册完成之后是这样的:
在这里插入图片描述
查阅官方文档,用法差不多,有语音合成和语音识别两种,我们这里先完成语音合成吧,官方文档整理的很好:
百度语音合成api1
百度语音合成api2
试一下

首先要下载它的库:

pip3 install baidu-api

然后我们给他定义成方法,传入参数text为要语音合成的文字

import os
import playsound	# 播放音频的库
from aip import AipSpeech		# 百度语音的api

""" 你的 APPID AK SK """
APP_ID = '!!!你的 App ID!!!'
API_KEY = '!!!你的 Api Key!!!'
SECRET_KEY = '!!!你的 Secret Key!!!'

client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)

# 语音合成
def speak_(text):
    result = client.synthesis(text, 'zh', 1, {
        'vol': 5,
        'per': 0,
        'spd': 5,
        'pit': 5
    })

    # 识别正确返回语音二进制 错误则返回dict 参照下面错误码
    if not isinstance(result, dict):
        with open('audio.mp3', 'wb') as f:
            f.write(result)
        playsound.playsound('audio.mp3')	# 播放
        os.remove('audio.mp3')		# 放完后删除音频文件,否则第二次会无法写入

再在respond(data)方法最后一行调用speak_(text)即可播放语音

(3)键盘监听+录音+保存wav文件

语音输入这部分我想实现的目标功能是:按一下键盘空格键开始录音,再按一下空格键录音结束,然后保存为一个.wav文件

1. 键盘监听

键盘监听我用的是pynput库
基本用法:

from pynput.keyboard import Listener


def check_input():
    with Listener(on_press=press) as listener:
        listener.join()

def press(key):
    # print(key.char)	# 按下键盘的操作,例如打印出按下的字符
    pass

2. 录音

录音我用的是pyaudio库

基本用法:

import pyaudio


"""pyaudio参数"""
CHUNK = 1024    # 数据包或者数据片段
FORMAT = pyaudio.paInt16    # pyaudio.paInt16表示我们使用量化位数 16位来进行录音
CHANNELS = 1    # 声道,1为单声道,2为双声道
RATE = 16000    # 采样率,每秒钟16000次
# RECORD_SECONDS = 5  # 录音时间

def recoder():
    _frames = []
    p = pyaudio.PyAudio()
    stream = p.open(channels=CHANNELS,
                    format=FORMAT,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK)
    while 1:
        data = stream.read(CHUNK)
        _frames.append(data)

    stream.stop_stream()
    stream.close()
    p.terminate()

需要注意的是,后面用到的百度语音识别双声道识别效果很差,设置单声道后就很好了,所以设置CHANNELS=1

还有后面用到的百度语音识别只支持16000采样率,所以RATE设置为16000

3. 保存.wav文件

用到wave库
结合pyaudio,基本用法:

import wave
import pyaudio


"""pyaudio参数"""
CHUNK = 1024    # 数据包或者数据片段
FORMAT = pyaudio.paInt16    # pyaudio.paInt16表示我们使用量化位数 16位来进行录音
CHANNELS = 1    # 声道,1为单声道,2为双声道
RATE = 16000    # 采样率,每秒钟16000次
# RECORD_SECONDS = 5  # 录音时间

def recoder():
    _frames = []
    p = pyaudio.PyAudio()
    stream = p.open(channels=CHANNELS,
                    format=FORMAT,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK)
    while 1:
        data = stream.read(CHUNK)
        _frames.append(data)

    stream.stop_stream()
    stream.close()
    p.terminate()

	# 保存wav文件
	wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(p.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(_frames))
    wf.close()
    # print("Saved")

(4)百度语音识别API

有了使用语音合成API的基础,用语音识别API就很easy
前面是一样的,所以我把他写在方法外,可以公用:

from aip import AipSpeech

""" 你的 APPID AK SK """
APP_ID = '你的 App ID'
API_KEY = '你的 Api Key'
SECRET_KEY = '你的 Secret Key'

client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)

后面就是语音识别,传入的参数是音频文件的二进制码,文件格式(我们用的是wav),采样率(只能用16000,所以前面的录音必须设置RATE为16000),语言类别(默认1537,普通话)。

# 读取文件
def get_file_content(filePath):
    with open(filePath, 'rb') as fp:
        return fp.read()

# 识别本地文件
client.asr(get_file_content(WAVE_OUTPUT_FILENAME), 'wav', 16000, {
    'dev_pid': 1537,
})

WAVE_OUTPUT_FILENAME就是我们刚才录音保存的文件名。

(5)浏览器搜索 + 打开常用软件功能

1. 浏览器搜索

打开浏览器,我们需要用到:webbrowser库,
没有的话自己pip安装一下
基本用法:

url = "www.baidu.com"
webbrowser.get().open(url)

我们前面已经能通过语音得到我们说的话text,当我们说的话text中有“搜索”两个字时,打开百度并搜索我们要搜的内容,实现代码:

if '搜索' in text:
	_, keywords = text.split('搜索')
	url = 'https://www.baidu.com/s?wd=' + keywords
	webbrowser.get().open(url)
else:
	...

2. 打开常用软件

windows打开软件有很多方法,参考:Python中四种运行其他程序的方式
我用的是win32api库,基本用法:
ShellExecute(hwnd, op, file, args, dir, show)

  • hwnd: 父窗口的句柄,如果没有父窗口,则为0
  • op : 要运行的操作,为open,print或者为空
  • file: 要运行的程序,或者打开的脚本
  • args: 要向程序传递的参数,如果打开的是文件则为空
  • dir : 程序初始化的目录
  • show: 是否显示窗口

盘他:

	if '搜索' in data:
		...
    elif '打开' in data:
        _, name = data.split('打开')
        # print(name)
        if '网易云' in name:
            win32api.ShellExecute(0, 'open', 'D:\App\CloudMusic\cloudmusic.exe', '', '', 1)    # 打开网易云,
        elif 'QQ' in name or 'qq' in name:
            win32api.ShellExecute(0, 'open', r'D:\App\QQ\Bin\QQScLauncher.exe', '', '', 1)    # 打开QQ
        elif '微信' in name:
            win32api.ShellExecute(0, 'open', r'D:\App\WeChat\WeChat.exe', '', '', 1)   # 打开微信

三、综合实现:

为了实现目标功能,还涉及到多线程,将键盘监听单独占用一个线程,设置run,初始值为False,当按下空格时,run变为True,再次按下时,run变为False,以此类推,当run为True时,开始录音,run为False时,录音结束。
运行效果图:(语音输入,语音输出)
运行效果图

全部完整代码:
(代码中api_key之类的要用你自己的噢)

import webbrowser
import requests
import json
import playsound
import os
import win32api
import pyaudio
import wave
import threading
from aip import AipSpeech
from pynput.keyboard import Listener


prologue = '主人您好,我是Niubility,爱你哦~'  # 开场白

"""图灵机器人API"""
urls = 'http://openapi.tuling123.com/openapi/api/v2'
key = '**********'
api_key = '********************'

count = 1   # 计数
run = False

"""pyaudio参数"""
CHUNK = 1024    # 数据包或者数据片段
FORMAT = pyaudio.paInt16    # pyaudio.paInt16表示我们使用量化位数 16位来进行录音
CHANNELS = 1    # 声道,1为单声道,2为双声道
RATE = 16000    # 采样率,每秒钟16000次
# RECORD_SECONDS = 5  # 录音时间
WAVE_OUTPUT_FILENAME = "output.wav"     # 保存录音文件名

""" 你的 APPID AK SK """
APP_ID = '**********'
API_KEY = '********************'
SECRET_KEY = '********************'

client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)		# 百度语音接口

def recoder():
    _frames = []
    global run
    p = pyaudio.PyAudio()
    stream = p.open(channels=CHANNELS,
                    format=FORMAT,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK)
    while run:
        data = stream.read(CHUNK)
        _frames.append(data)

    stream.stop_stream()
    stream.close()
    p.terminate()

    wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(p.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(_frames))
    wf.close()
    # print("Saved")

# 按键按下空格时控制录音功能开关
def press(key):
    global run
    try:
        # print(str(key))
        if str(key) == 'Key.space':
            run = not run

            # print(run)
    except AttributeError as e1:
        print(e1)
        pass

# 监听键盘
def check_input():
    with Listener(on_press=press) as listener:
        listener.join()


# 读取录音文件字节码
def get_file_content(filePath):
    with open(filePath, 'rb') as fp:
        return fp.read()

# 识别录音,语音转文字
def lic():
    res = client.asr(get_file_content(WAVE_OUTPUT_FILENAME), 'wav', 16000, {
        'dev_pid': 1537,
    })
    text = res['result'][0]
    # print(text)
    return text

# 语音合成
def speak_(text):
    result = client.synthesis(text, 'zh', 1, {
        'vol': 5,
        'per': 0,
        'spd': 5,
        'pit': 5
    })

    # 识别正确返回语音二进制 错误则返回dict 参照下面错误码
    if not isinstance(result, dict):
        with open('audio.mp3', 'wb') as f:
            f.write(result)
        playsound.playsound('audio.mp3')
        os.remove('audio.mp3')

# 第一层判别
def respond(data):

    if '歌' in data:
        douban_rul = 'https://douban.fm/'
        webbrowser.get().open(douban_rul)
    elif '搜索' in data:
        _, keywords = data.split('搜索')
        baidu_url = 'https://www.baidu.com/s?wd=' + keywords
        webbrowser.get().open(baidu_url)
    elif '打开' in data:
        _, name = data.split('打开')
        # print(name)
        if '网易云' in name:
            win32api.ShellExecute(0, 'open', 'D:\App\CloudMusic\cloudmusic.exe', '', '', 1)    # 打开网易云
        elif 'QQ' in name or 'qq' in name:
            win32api.ShellExecute(0, 'open', r'D:\App\QQ\Bin\QQScLauncher.exe', '', '', 1)    # 打开QQ
        elif '微信' in name:
            win32api.ShellExecute(0, 'open', r'D:\App\WeChat\WeChat.exe', '', '', 1)   # 打开微信

    else:
        data_dict = {
            "reqType":0,
            "perception": {
                "inputText": {
                    "text": data
                },
            },
            "userInfo": {
                "apiKey": api_key,
                "userId": "!!!你自己的userId!!! "
            }
        }
        result = requests.post(urls, json=data_dict)
        content = (result.content).decode('utf-8')
        # print(content)
        ans = json.loads(content)
        text = ans['results'][0]['values']['text']
        print('Niubility:',text)
        speak_(text)


if __name__ == '__main__':

    print('Niubility:', prologue)
    speak_(prologue)
    print('{}//你:按  空格  键开始说话...'.format(count), end=' ')

    t1 = threading.Thread(target=check_input,args=())      # 监听键盘
    t1.start()

    while 1:

        if run is True:
            print('\r{}//你:正在听呢,说完了请再按  空格  键...'.format(count), end=' ')
            recoder()
            text2 = lic()
            print('\r{}//你:'.format(count),text2)
            respond(text2)
            count += 1
            print('{}//你:按  空格  键开始说话...'.format(count), end=' ')
;