【Python + Flask + Web录音 + baidu-aip + 图灵机器人 = 人机对话】

强颜欢笑 提交于 2019-11-27 18:53:27

原文: http://blog.gqylpy.com/gqy/351

"安装baidu-aip:pip install baidu-aip

百度云网址:https://login.bce.baidu.com
百度语音合成文档:https://ai.baidu.com/docs#/TTS-Online-Python-SDK/top
百度语音识别文档:https://ai.baidu.com/docs#/ASR-Online-Python-SDK/top
百度自然语言处理基础技术文档:https://ai.baidu.com/docs#/NLP-Python-SDK/6dfe1b04
Mac系统安装ffmpeg文档:https://blog.csdn.net/stonenotes/article/details/68958332
图灵机器人官网:http://www.tuling123.com/
图灵机器人接口说明:https://www.kancloud.cn/turing/www-tuling123-com/718227

后端代码

# ⚠️这是在macOS系统上(版本10.14)写的

# pip install baidu-aip

# 百度云:https://login.bce.baidu.com
# 百度语音合成文档:https://ai.baidu.com/docs#/TTS-Online-Python-SDK/top
# 百度语音识别文档:https://ai.baidu.com/docs#/ASR-Online-Python-SDK/top
# 百度自然语言处理基础技术文档:https://ai.baidu.com/docs#/NLP-Python-SDK/6dfe1b04
# Mac系统安装ffmpeg文档:https://blog.csdn.net/stonenotes/article/details/68958332
# 图灵机器人官网:http://www.tuling123.com/
# 图灵机器人接口说明:https://www.kancloud.cn/turing/www-tuling123-com/718227


import os
import time
import uuid
import requests
import subprocess
from aip import AipSpeech, AipNlp  # baidu-aip
from flask import Flask, request, render_template, send_file, jsonify


# =======================下面是百度语音合成/识别=======================

class VoiceTextConversion(object):
    # 这3个私有属性是本人的百度应用的appID等信息
    __APP_ID = '15225447'
    __API_KEY = 's5m43BMMEGGPaFGxeX3SsY7m'
    __SECRET_KEY = 'Lca9FEGpWNZW6yd8WWAHAyCyLovmi6rb'
    text_to_voice_error_info = {500: '不支持的输入', 501: '输入的参数不正确', 502: 'token验证失败', 503: '合成后端错误'}

    def __init__(self, app_id=None, api_key=None, secret_key=None,
                 connect_timeout=None, socket_timeout=None, is_external_called=False):
        """
        :param APP_ID: 请使用你自己的百度应用
        :param API_KEY: 请使用你自己的百度应用
        :param SECRET_KEY: 请使用你自己的百度应用
        :param connectTimeout: 建立连接的超时时间(单位:毫秒,1000毫秒=1秒)默认60秒
        :param socketTimeout: 通过打开的连接传输数据的超时时间(单位:毫秒)默认60秒
        :param is_external_called: 当前对象是否为其它应用调用
        """
        self.__is_external_called = is_external_called
        if requests.get('http://www.baidu.com').status_code is not 200:
            exit(self.__custom_print("没网你玩个锤子啊!"))
        # 如果你有自己的百度语音应用,请使用你自己的百度语音应用,如果你没有,请去注册(免费):
        self.client = AipSpeech(app_id, api_key, secret_key) if app_id \
            else AipSpeech(self.__APP_ID, self.__API_KEY, self.__SECRET_KEY)
        # 可设置链接/传输的超时时间:
        connect_timeout and self.client.setConnectionTimeoutInMillis(connect_timeout)
        socket_timeout and self.client.setSocketTimeoutInMillis(socket_timeout)

    def text_to_voice(self, text, filepath='语音文件', lang='zh', ctp=1, per=3, pid=5, spd=5, vol=5):
        """
        文字 -> 语音
        :param text: 要转换的文字
        :param filename: 保存路径,自动添加 .mp3 后缀
        :param lang: 语言,默认中文
        :param ctp: ?
        :param per: 发音人,0为女声;1为男声;3为情感合成-度逍遥;4为情感合成-度牙牙
        :param pid: 音调,取值0-9,默认为5 中语速
        :param spd: 语速,取值0-9,默认为5 中语速
        :param vol: 音量,取值0-15,默认为5 中音量
        """
        voice_format = {'per': per, 'pid': pid, 'spd': spd, 'vol': vol}
        # 开始转换:
        # 转换成功时返回bytes语音数据;转换失败时返回错误信息字典;
        result = self.client.synthesis(text, lang, ctp, voice_format)
        # 如果转换成功,将保存至磁盘:
        if isinstance(result, bytes):
            with open(f'{filepath}.mp3', 'wb') as f:
                f.write(result)
            self.__custom_print(f'合成语音成功:{filepath}', color=32, is_error=False)
        # 如果出现错误:
        else:
            err_code = result.get('err_no')
            err_info = self.text_to_voice_error_info[err_code]
            self.__custom_print(err_info)

    def voice_to_text(self, filepath=None, format='pcm', dev_pid=1536):
        """
        语音 -> 文字
        使用前请安装ffmpeg,用于将其它格式的语音文件转换为 .pcm 格式的语音文件
        :param filepath: 语音文件路径
        :param format: 语音文件格式(后缀),.pcm 或 .wav 或 .amr 不区分大小写。推荐使用 .pcm 文件
        :param dev_pid: 语言,1536普通话(支持简单的英文识别),1537普通话,1737英语,1637粤语,1837四川话,1936普通话远场(距离识别)
        :return:
        """
        # 如果输入的文件路径有误
        if not os.path.isfile(filepath):
            return self.__custom_print('语音文件路径错误')
        # 将语音文件转换为 format 格式,这一步骤将调用系统命令ffmpeg并会生成新文件,需要安装该命令(详见顶部:Mac系统安装ffmpeg文档链接)
        cmd = f'ffmpeg -y -i {filepath} -acodec pcm_s16le -f s16le -ac 1 -ar 16000 {filepath}.{format}'
        # ffmpeg命令执行过程中输出的信息被保存在stderr中
        r = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE)
        # 命令执行成功,将会生成 format 格式的语音文件
        # 语音文件生成的很慢,这个很奇怪的问题,先这么解决吧:
        for i in range(999):
            time.sleep(0.1)
            if os.path.isfile(f'{filepath}.{format}'):
                break
        # 读取转换后的语言文件:
        with open(f'{filepath}.{format}', 'rb') as fp:
            voice_content = fp.read()
        # 读取后,删除生成的 format 格式的文件
        os.remove(f'{filepath}.{format}')
        # 开始转换:
        result = self.client.asr(voice_content, format, 16000, {'dev_pid': dev_pid})
        # 如果转换成功,直接输出文字:
        if not result.get('err_no'):
            content = result.get('result')[0]
            if self.__is_external_called:
                return content
            self.__custom_print(content, 34, 1, is_error=False)
        # 如果转换错误
        else:
            err_code = result.get('err_no')
            self.__custom_print(f'错误代码:{err_code},详见顶部:百度语音识别文档')

    def short_text_compare(self, text1, text2):
        """
        短文本相似度,详见顶部:百度自然语言处理基础技术文档
        超过72% 极度相似
        超过60% 比较相似
        超过60% 可能相似
        低于50% 不相似
        越低越不相似
        :param text1: 第一个短文本
        :param text2: 第二个短文本
        :return: 相似百分比
        """
        nlp_client = AipNlp(self.__APP_ID, self.__API_KEY, self.__SECRET_KEY)
        # 传送短文本
        result = nlp_client.simnet(text1, text2)
        # 获取相似度
        score = result.get('score')
        # 转换百分比
        score = f'{round(score * 100)}%'
        self.__custom_print(f'相似度:{score}', 35, 1, is_error=False)

    def __custom_print(self, data=None, color=31, font=0, is_error=True):
        """
        自定义打印信息的字体及颜色
        :param color: 打印颜色,默认红色,32绿色,34蓝色,35紫红色,更多打印颜色请百度
        :param font: 打印字体,默认正常字体,1为黑体(加粗)
        :param is_error: 是否打印错误信息
        """
        if self.__is_external_called:
            return
        is_error and self.__custom_print('错误!!!', font=1, is_error=False)
        print_info = f'\033[{font};{color};0m{data}\033[0m'
        print(print_info)


# ---------开始使用---------

# if __name__ == '__main__':
#     # 我们先实例化一个对象
#     obj = VoiceTextConversion()
#
#     # 文字 -> 语音
#     text = '人活着就要有梦想,不然跟咸鱼又有什么区别呢?'
#     obj.text_to_voice(text, filepath='语音文件')
#
#     # 语音 -> 文字
#     obj.voice_to_text('语音文件.mp3')
#
#     # 短文本相似度
#     text1, text2 = '你叫什么名字', '你的名字叫什么'
#     obj.short_text_compare(text1, text2)


# =======================下面是图灵机器人=======================


class TuringRobot(object):  # 玩转图灵机器人,详见顶部:图灵机器人接口说明
    # 机器人标示,如果你有自己的机器人,请使用你自己的机器人
    __API_KEY = '8c512c52ac324a3eb4c40639d43bd507'  # 免费版 100次/天
    # 图灵机器人接入地址
    URL = 'http://openapi.tuling123.com/openapi/api/v2'
    # 请求参数
    POST_DATA = {
        "reqType": 0,  # 输入类型:0-文本(默认) 1-图片 2-音频
        "perception": {  # 输入信息
            "inputText": {  # 文本信息
                "text": ""  # 直接输入文本,1-128个字符
            },
        },
        "userInfo": {  # 用户参数
            "apiKey": "",  # 机器人标示
            "userId": "",  # 用户唯一标示,用于请求上下文判断是否为同一个用户,不可重复
        }
    }

    @classmethod
    def run(cls, text='你的名字叫什么', user_id='user01', api_key=None, *args, **kwargs):
        """
        开始玩转图灵机器人
        :param text: 你对机器人说的话
        :param api_key: 图灵机器人标示,如果你有自己的机器人,请使用你自己的机器人
        :param user_id: 用户唯一ID,用于请求上下文判断是否为同一个用户,多个用户使用时不可重复
        :return: 直接返回机器人回的话
        """
        cls.POST_DATA.get('userInfo')['apiKey'] = api_key or cls.__API_KEY
        cls.POST_DATA.get('userInfo')['userId'] = user_id
        cls.POST_DATA.get('perception')['inputText']['text'] = text
        result = requests.post(cls.URL, json=cls.POST_DATA).json()
        return result.get('results')[0]['values']['text']


# =======================Flase App=======================


app = Flask(__name__)


@app.route('/', methods=['GET', 'POST'])
def index():
    return render_template('web.html')


@app.route('/toy_uploader', methods=['GET', 'POST'])
def toy_uploader():
    uuid4 = uuid.uuid4()
    vct = VoiceTextConversion(is_external_called=True)
    # 确保文件唯一,录音文件为 .wav 格式
    filename = f'{uuid4}.wav'
    # 保存语音文件
    request.files['record'].save(filename)
    # 开始语音转文字
    content = vct.voice_to_text(filename)
    os.remove(filename)
    # 与图灵机器人对话
    result = TuringRobot.run(text=content, user_id='user01')
    # 开始文字转语音
    vct.text_to_voice(text=result, filepath=uuid4)
    # 返回给前端语音文件名
    return jsonify({'filename': f'{uuid4}.mp3'})


@app.route('/get_audio/<filename>')
def get_audio(filename):
    # 前端ajax通过文件名来获取语音文件
    ret = send_file(filename)
    os.remove(filename)
    return ret


if __name__ == '__main__':
    app.run('0.0.0.0', 5000, debug=True)

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>人机对话</title>
</head>
<body>
<audio src="" controls autoplay id="player"></audio>
<div>
    <button onclick="start_reco()" style="background-color: red">录制语音</button>
</div>
<div>
    <button onclick="stop_reco()" style="background-color: green">发送语音</button>
</div>
</body>
<script type="text/javascript" src=https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js></script>
<script type="text/javascript" src="/static/Recorder.js"></script>
<script type="text/javascript">
    var serv = "http://127.0.0.1:5000";
    // var get_music = "http://192.168.1.102:9527/get_music/";
    // var get_chat = "http://192.168.1.102:9527/get_chat/";
    var reco = null;

    var audio_context = new AudioContext();
    navigator.getUserMedia = (navigator.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia ||
        navigator.msGetUserMedia);

    navigator.getUserMedia({audio: true}, create_stream, function (err) {
        console.log(err)
    });

    function create_stream(user_media) {
        var stream_input = audio_context.createMediaStreamSource(user_media);
        reco = new Recorder(stream_input);
    }

    function start_reco() {
        reco.record();
    }

    function stop_reco() {
        reco.stop();
        get_audio();
        reco.clear();
    }

    function get_audio() {
        reco.exportWAV(function (wav_file) {
            // wav_file = Blob对象 file对象
            // ws.send(wav_file);
            var formdata = new FormData();
            formdata.append("record", wav_file);
            // formdata.append("sender", toy_id);
            // formdata.append("to_user", document.getElementById("from_user").innerText);
            $.ajax({
                url: serv + "/toy_uploader",  // ⚠️
                type: 'post',
                processData: false,
                contentType: false,
                data: formdata,
                dataType: 'json',
                success: function (data) {
                    console.log(data.A);
                    // console.log(data);
                    document.getElementById("player").src =
                        "http://127.0.0.1:5000/get_audio/" + data.filename;  // ⚠️
                }
            })
        })
    }
</script>
</html>

前端依赖插件:
Recorder.js:https://download.csdn.net/download/qq_41964425/10867911
uuid.js:https://download.csdn.net/download/qq_41964425/10867907

使用火狐浏览器录音.
"

原文: http://blog.gqylpy.com/gqy/351

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!