1. 程式人生 > >itchat個人練習 語音與文字圖靈測試例程

itchat個人練習 語音與文字圖靈測試例程

背景介紹

itchat是一個開源的微信個人號介面,使用python呼叫微信從未如此簡單。

使用不到三十行的程式碼,你就可以完成一個能夠處理所有資訊的微信機器人。

官方文件參考https://itchat.readthedocs.io/zh/latest/

最近要做一個自動應答機器人,獲得使用者訊息GUI+語義分析+機器學習給出答案。

準備工作

需要安裝ffmpeg(百度搜索官網,下載windows版解壓後把bin目錄新增到系統變數的path中)
pip安裝 pydub,SpeechRecognition

pip install pydub
pip install SpeechRecognition

繫結訊息

GUI這部分使用微信的itchat介面,安裝和新手教程可以自己參考官方文件。

繫結語音訊息回覆的方式為:

@itchat.msg_register(RECORDING)
def tuling_reply(msg):

 其中用的是RECORDING是因為之前程式碼最開始有from itchat.content import *,否則就要使用itchat.content.RECORDING

關於@修飾符的作用,網上百度就有,說下自己的思考:

    @de
    def func1:
    ----- 等價於 ------
    func1 = de( func1 )

Python直譯器讀到函式修飾符“@”的時候,後面步驟會是這樣了:

1. 去呼叫de函式,de函式的入口引數就是那個叫“func1”的函式;

2. de函式被執行,入口引數的(也就是func1函式)會被呼叫(執行);

換言之,修飾符帶的那個函式的入口引數,就是下面的那個整個的函式。

參考https://blog.csdn.net/972301/article/details/59537712和 https://blog.csdn.net/fwenzhou/article/details/8733857

所以我們使用@的時候,itchat.msg_register這個函式就被執行了,我們定義的tuling_reply作為引數傳了進去,所以才會讀取到訊息就用這個函式處理訊息

 

語音識別

由於微信儲存的語音訊息都是mp3格式,看了一圈發現只有騰訊語音識別支援mp3,之前嘗試過騰訊一句話識別語音API,但是官方沒有最新的例程,並且居然不同部分用的是不同版本的文件說明,導致我鑑權一直失敗。到後來仔細研讀了下,自己寫了程式碼,鑑權應該是通過了,但是返回的訊息是x‘\98'這樣的一箇中文字元,並且解碼會失敗,這才發現可能是因為騰訊的只支援中文,雖然我在這個隨筆的例子是中文語音識別,但我實際專案要做的是英文語音識別。不過在這中間也學到了一些東西,比如加密演算法的使用,還有python3的二進位制和字串訊息的轉換關係。

 1 import binascii
 2 import hashlib
 3 import hmac
 4 import urllib.parse
 5 import urllib.request
 6 import time
 7 import random
 8 import base64
 9 
10 def asr(msg):
11     msg['Text'](msg['FileName'])#儲存mp3語音
12     timeData = str(int(time.time())) # 時間戳
13     nonceData = int(random.random()*10000) # Nonce,官網給的資訊:隨機正整數,與 Timestamp 聯合起來, 用於防止重放攻擊
14     with open(msg['FileName'], 'rb') as f:
15         voiceData = f.read()#讀取mp3語音,獲得byte資料,格式是b'\x..'
16     os.remove(msg['FileName'])#刪除mp3語音
17     DataLenData = len(voiceData)#讀取未base64編碼之前的檔案長度
18     tmp = int(timeData)#time stamp
19     signDictData = {#需要注意的是字典的key值要按照ascii碼升序排序,並不一定是字典序,可以使用sorted(signDictData.keys())來檢視ascii碼排序結果
20         'Action' : actionData,
21         'Data': base64.b64encode(voiceData).decode('utf8'),#base64編碼,編碼後是二進位制,再用decode解碼
22         # 'Data': voiceData,
23         'DataLen': DataLenData,
24         'EngSerViceType': EngSerViceTypeData,
25         'Nonce' : nonceData,
26         'ProjectId':0,
27         'Region': 'ap-shanghai',
28         'SecretId' : secretId,
29         # 'SignatureMethod': 'HmacSHA256',#加密演算法可選,不指定這個引數預設是HmacSHA1加密
30         'SourceType': SourceTypeData,
31         'SubServiceType': SubServiceTypeData,
32         'Timestamp' : tmp,
33         'UsrAudioKey': UsrAudioKeyData,
34         'Version': versionData,
35         'VoiceFormat': VoiceFormatData
36     }
37     #   請求方法 + 請求主機 +請求路徑 + ? + 請求字串
38     requestStr = "%s%s%s%s%s"%(requestMethod,uriData,"/","?",dictToStr(signDictData))
39     # signData = urllib.parse.quote(sign(secretKey,requestStr,'HmacSHA1'))
40     #生成簽名字元的時候一定是使用的沒有經過urlencode編碼的requestStr字串,下面的加了encode的就是把字串變成byte,sha1是演算法,decode是把二進位制解碼為字串。digest()是把hmac.new()的結果解析成字串,然後經過base64編碼為byte,再解碼為字串
41     signData = binascii.b2a_base64(hmac.new(secretKey.encode('utf-8'), requestStr.encode('utf-8'), hashlib.sha1).digest())[:-1].decode()
42     # 上述操作是實現簽名,下面即進行請求
43     # 先建立請求引數, 此處引數只在簽名時多了一個Signature
44     actionArgs = {
45         'Action' : actionData,
46         'Data': base64.b64encode(voiceData).decode('utf8'),
47         # 'Data': voiceData,
48         'DataLen': DataLenData,
49         'EngSerViceType': EngSerViceTypeData,
50         'Nonce' : nonceData,
51         'ProjectId':0,
52         'Region': 'ap-shanghai',
53         'SecretId' : secretId,
54         'SourceType': SourceTypeData,
55         'SubServiceType': SubServiceTypeData,
56         'Timestamp' : tmp,
57         'UsrAudioKey': UsrAudioKeyData,
58         'Version': versionData,
59         'VoiceFormat': VoiceFormatData,
60         "Signature": signData
61     }
62     # 根據uri構建請求的url
63     requestUrl = "https://%s/?"%(uriData)
64     # 將請求的url和引數進行拼接,使用urlencode會修改掉引數中的/和=等符號的表示方式
65     requestUrlWithArgs = requestUrl + urllib.parse.urlencode(actionArgs)
66 
67     # actionArgs = signDictData #這是深複製,兩個字典就是一個字典
68     # actionArgs["Signature"] = signData
69 
70     # # 根據uri構建請求的url
71     # requestUrl = "https://%s/?"%(uriData)
72     # # 將請求的url和引數進行拼接
73     # requestUrlWithArgs = requestUrl + dictToStr(actionArgs)
74 
75     # 獲得response
76     responseData = urllib.request.urlopen(requestUrlWithArgs).read().decode("utf-8")# 根據uri構建
77     # return json.loads(responseData)["Response"]["Error"]["Message"] #處理錯誤訊息
78     return json.loads(responseData)["Response"]["Result"]#處理正確訊息
讀取語音檔案和騰訊API語音識別

 後來一直在找能不能用別的語音api,由於百度的參考文件最多,我在其中就發現大家為了能夠把音訊發到百度語音api上,就使用了pydub對原音訊檔案進行了轉碼,這樣我們就可以傳送wav格式的語音,由於本來是想識別英文呢語音的,所以我還是嘗試外國公司的api,嘗試了微軟語音識別,7天免費的那個,官方文件對於REST介面的參考太少了,並且都不是python的,這時候我在github上發現了一個SpeechRecognition專案,原來以為是隻有谷歌語音識別的介面,嘗試了一下結果果然被牆了,用了代理之後還是無法訪問,然後我就看了github主頁的Transcribe an audio file,在裡面找到了不止一個介面,其中就有Microsoft Bing Voice Recognition的例程,呼叫非常簡單,只需要語音檔案和金鑰,並且支援語音檔案的格式轉碼,自動給你轉成對應必應api的語音引數格式,各位可以自己進入r.recognize_bing()函式定義,在裡面詳細描述瞭如何使用必應語音服務(需要注意的是微軟一元試用雲服務的活動不支援必應語音識別這個模組),在這裡把原話複製下來供參考:

"""
        Performs speech recognition on ``audio_data`` (an ``AudioData`` instance), using the Microsoft Bing Speech API.

        The Microsoft Bing Speech API key is specified by ``key``. Unfortunately, these are not available without `signing up for an account <https://azure.microsoft.com/en-ca/pricing/details/cognitive-services/speech-api/>`__ with Microsoft Azure.

        To get the API key, go to the `Microsoft Azure Portal Resources <https://portal.azure.com/>`__ page, go to "All Resources" > "Add" > "See All" > Search "Bing Speech API > "Create", and fill in the form to make a "Bing Speech API" resource. On the resulting page (which is also accessible from the "All Resources" page in the Azure Portal), go to the "Show Access Keys" page, which will have two API keys, either of which can be used for the `key` parameter. Microsoft Bing Speech API keys are 32-character lowercase hexadecimal strings.

        The recognition language is determined by ``language``, a BCP-47 language tag like ``"en-US"`` (US English) or ``"fr-FR"`` (International French), defaulting to US English. A list of supported language values can be found in the `API documentation <https://docs.microsoft.com/en-us/azure/cognitive-services/speech/api-reference-rest/bingvoicerecognition#recognition-language>`__ under "Interactive and dictation mode".

        Returns the most likely transcription if ``show_all`` is false (the default). Otherwise, returns the `raw API response <https://docs.microsoft.com/en-us/azure/cognitive-services/speech/api-reference-rest/bingvoicerecognition#sample-responses>`__ as a JSON dictionary.

        Raises a ``speech_recognition.UnknownValueError`` exception if the speech is unintelligible. Raises a ``speech_recognition.RequestError`` exception if the speech recognition operation failed, if the key isn't valid, or if there is no internet connection.
        """
Bing語音識別使用說明

所以我們只需要獲得正確的金鑰,呼叫這個函式就可以啦,要注意的是中文語音識別需要在傳入引數中設定language="zh-CN"

程式碼

全程式碼如下:

# -*- coding: UTF-8 -*-
import requests
import itchat
import json
from itchat.content import *
import os
import speech_recognition as sr
from pydub import AudioSegment

def get_response_tuling(msg):
    # 這裡我們就像在“3. 實現最簡單的與圖靈機器人的互動”中做的一樣
    # 構造了要傳送給伺服器的資料
    apiUrl = 'http://www.tuling123.com/openapi/api'
    data = {
        'key'    : '8edce3ce905a4c1dbb965e6b35c3834d',
        'info'   : msg,
        'userid' : 'wechat-robot',
    }
    try:
        r = requests.post(apiUrl, data=data).json()
        # 字典的get方法在字典沒有'text'值的時候會返回None而不會丟擲異常
        return r.get('text')
    # 為了防止伺服器沒有正常響應導致程式異常退出,這裡用try-except捕獲了異常
    # 如果伺服器沒能正常互動(返回非json或無法連線),那麼就會進入下面的return
    except:
        # 將會返回一個None
        return

def asr(msg):
    #語音訊息識別轉文字輸出
    msg['Text'](msg['FileName'])
    song = AudioSegment.from_mp3(msg['FileName'])
    song.export("tmp.wav", format="wav")
    r = sr.Recognizer()
    with sr.AudioFile('tmp.wav') as source:
        audio = r.record(source) # read the entire audio file
    os.remove('tmp.wav')
    os.remove(msg['FileName'])
    # recognize speech using Microsoft Bing Voice Recognition
    BING_KEY = "======修改成你自己的金鑰======="  # Microsoft Bing Voice Recognition API keys 32-character lowercase hexadecimal strings
    try:
        text = r.recognize_bing(audio, key=BING_KEY,language="zh-CN")
        print("Microsoft Bing Voice Recognition thinks you said " + text)
        return text
    except sr.UnknownValueError:
        print("Microsoft Bing Voice Recognition could not understand audio")
    except sr.RequestError as e:
        print("Could not request results from Microsoft Bing Voice Recognition service; {0}".format(e))

@itchat.msg_register(TEXT)#因為之前把itchat.content全部import了,裡面有TEXT變數
def tuling_reply_text(msg):
    # 註冊文字訊息獲取後的處理
    # 為了保證在圖靈Key出現問題的時候仍舊可以回覆,這裡設定一個預設回覆
    defaultReply = 'I received a: ' + msg['Text']
    return get_response_tuling(msg['Text']) or defaultReply

@itchat.msg_register(RECORDING)
def tuling_reply(msg):
    # 註冊語音訊息獲取後的處理
    # 為了保證在圖靈Key出現問題的時候仍舊可以回覆,這裡設定一個預設回覆
    defaultReply = 'I received a: ' + msg['Type']

    # 如果圖靈Key出現問題,那麼reply將會是None
    asrMessage = asr(msg)
    return get_response_tuling(asrMessage) or defaultReply

# 為了讓實驗過程更加方便(修改程式不用多次掃碼),我們使用熱啟動hotReload=True
itchat.auto_login(hotReload=True)
itchat.run()