[譯] 使用 Nexmo 和微軟語音翻譯 API 構建 Babel Fish

Starrier發表於2018-09-01

如果在過去的幾個月時間裡你關注過網際網路上的變化,那你就會注意到 Google 的即時翻譯 Pixel Buds。它是一個像給 Galaxy 的 Hitchhiker 指南中 Bable Fish 一樣的技術,可以為穿戴者翻譯任何可感知的語言並讓他們像虛擬人類一樣與穿戴者進行交流。但使用 Google 的 Pixel Buds 代價昂貴 —— 那麼我們為何不自己動手構建呢?這也是 Danielle 和我最近在 hackference 上所構想的。我們想要去建立一個讓電話交流的雙方可以根據自己所需,聽到彼此說話內容的翻譯版本的 Nexmo Babel Fish。

Image of a Babel fish from The Hitchhiker's Guide to the Galaxy

來自給 Galaxy 的 Hitchhiker 的指南中的 Bable Fish 的圖片。

在這篇部落格中,我們將介紹搭建 Babel Fish 系統的步驟。首先我們需要設定和配置環境。然後我們會設定一個 Nexmo number 來處理呼叫。在此之後,我們會搭建一個通過 WebSocket 接收語音,並將其從 Nexmo number 傳送到微軟語言翻譯 API 的 Python 伺服器。我們會使用語音翻譯 API 來處理轉錄和翻譯。在此基礎上,我們將實現管理雙向對話的邏輯,並指示 Nexmo number 說出翻譯內容。為了便於實現,雙方必須都呼叫我們的 Nexmo number。你可以參考下面的高階圖表,它展示了來自任何一方的語音例項是如何處理的。注意在本教程中,我會使用一個德語/英語的示例。

Diagram that shows how a message passes through the system. A German caller speaks a message in German which Nexmo passes through to a Python server. The Python server sends the German audio to the Microsoft Speech API. The Speech API responds by sending the English translation as text to the Python server. The Python server then sends a request to Nexmo to speak the English message to the British caller. At this point the British caller hears the translated message in English.

該圖表展示了訊息如何在系統中傳遞。一個德語呼叫者通過 Nexmo 傳送一條德語訊息給 Python 伺服器。Python 伺服器將德語音訊傳送給微軟語音 API。語音 API 將英文翻譯作為文字傳送到 Python 伺服器來響應請求。Python 伺服器會向 Nexmo 傳送請求,向英語呼叫者說出英語訊息。此時英語呼叫者便會聽到翻譯後的英語資訊。

如果你只想看程式碼,可以在 Github 上找到。

準備條件

你需要同時安裝 Python 2.x 或 3.x 和 HTTP 通道軟體 ngrok。我們會列出你在安裝過程中需要的所有命令。

開始

配置你的環境

我們從使用 Virtualenv 來為此專案配置虛擬環境的 DIY Babel Fish 的解決方案開始。Virtualenv 允許我們將此專案與其他專案隔離。繼續為此專案建立目錄,並將以下依賴列表複製到你專案目錄中命名為 requirements.txt 的檔案中:

nexmo
tornado>=4.4.2
requests>=2.12.4
複製程式碼

為了建立並啟用你的虛擬環境,請在終端執行以下命令:

virtualenv venv  # sets up the environment
source venv/bin/activate  # activates the environment
pip install -r requirement.txt  # installs our dependencies
 
# 如果你使用的是 Python 3,請使用以下命令
pip3 install -r requirement.txt
複製程式碼

此時,通過在單獨開啟一個終端的視窗中執行下列命令來啟動 ngrok。ngrok 允許我們將本地 5000 埠暴露給外部請求。為此,你需要讓 ngrok 在後臺保持正常執行。你可以閱讀更多關於用 Nexmo 連結 ngrok 的資訊

ngrok http 5000
複製程式碼

一旦你執行上述命令,你的終端就會和下面的截圖類似。下一步,你需要在配置你的 Nexmo 應用程式和編號 時轉發 URL。

Screenshot of ngrok running in a terminal and displaying a forwarding URL of the form “http://016a0331.ngrok.io”.

在終端執行的 ngrok 以及展示轉發來自 “016a0331.ngrok.io” URL 的螢幕截圖。

獲取一個 Nexmo number

使用我們的翻譯服務需要一個 Nexmo Number。如果你還沒有賬號,可以在 dashboard.nexmo.com/sign-up 進行註冊,請前往 dashboard.nexmo.com/buy-numbers 購買一個具有語音功能的 Nexmo number。

Screen capture of a user buying a number using the Nexmo buy numbers menu. A user selects their country, Voice as the feature, and mobile as the type and clicks on the search button. The user then clicks on buy for the first number that comes up and confirms the purchase.

該截圖展示了使用者如何使用 Nexmo 的購買編號選單來購買 Nexmo number。使用者需要選擇國家和語音將作為特徵,移動裝置作為型別,最後點選搜尋按鈕。然後點選第一個數字邊的購買連結,便可確認購買。

建立 Nexmo 應用程式

進入你的應用程式,然後新增一個。對事件 URL 和應答 URL 使用 Ngrok 轉發 URL,新增 /event 作為事件 URL(e.g. http://016a0331.ngrok.io/event) 的路徑,對應答 URL(e.g. http://016a0331.ngrok.io/ncco) 使用 /ncco。我們之後會設定這些端點。在你的電腦上通過使用者介面生成並儲存一對公鑰/私鑰對。

Screen capture of a user creating an application using the Nexmo application menu. A user clicks on add new application. In the form that appears the user enters babelfish as the application name, `http://016a0331.ngrok.io/event` as the Event URL, and `http://016a0331.ngrok.io/ncco` as the Answer URL. The user then clicks on the `Generate public/private key pair` link, saves the key when prompted, and finally clicks on create application.” The last step for our number setup is to link the number you purchased earlier to your application. Use the application dashboard to link the number.

使用 Nexmo 應用程式目錄建立應用程式的使用者螢幕截圖。使用者點選新增應用程式。在顯示的表單中,使用者輸入 Babelfish 作為應用程式名,http://016a0331.ngrok.io/event 作為事件的 URL,http://016a0331.ngrok.io/ncco 作為應答 URL。然後使用者單擊 Generate public/private key pair 連結,在提示時儲存金鑰,最後單擊建立應用程式。編號設定的最後一步是將你之前購買的編號連結到你的應用程式。使用應用程式儀表板來連結編號。

獲取微軟語音翻譯 API 的金鑰

我們需要設定的另一個服務是微軟的語音服務 API。在 azure.com 上註冊一個免費的微軟 Azure 賬號,然後跳轉到 portal.azure.com,建立一個語音翻譯 API 資源。你需要它為下一步生成的金鑰。

Screen capture of a user setting up the Microsoft Translator Speech API. A user types translator speech into the Marketplace search on the Microsoft Azure portal. The user then clicks on the Translator Speech API option that comes up and clicks on the create button on the API overview screen. The user then fills in the form for the resource using babelfish as the name, Pay-as-you-go as the subscription, F0 (10 Hours of audio input) as the pricing tier, and babelfish-resource as the resource group name. After checking the box that the user has 'read and understood the notice' and checking add to dashboard, the user clicks on create and is redirected to the dashboard. After the deployment finishes, the user clicks on the deployed resource and is presented with a resource dashboard. On the resource dashboard under the section grab the keys the user clicks on keys and copies key 1.

設定微軟語音翻譯 API 的使用者螢幕截圖。使用者在微軟 Azure 上的市場搜尋中輸入語言翻譯。然後,使用者單擊出現的語音翻譯 API 選項,再單擊 API 概述螢幕上的建立按鈕。之後,使用者填寫資源的表單,使用 babelfish 作為應用程式名,Pay-as-you-go 作為訂閱,F0(10 小時的音訊輸入)作為定價層,babelfish —— 資源作為資源組組名。選中使用者已經“閱讀並理解注意事項”的選框,並檢查新增到儀表板,使用者單擊建立並重定向到儀表板。部署完成後,使用者單擊已部署的資源,會顯示一個資源儀表板。在資源儀表板中,抓取使用者單擊的鍵並複製鍵 1。

管理金鑰和配置

我們現在有了自己的 Nexmo number 和語音翻譯 API 金鑰。我們現在要做的就是去設定一個包含所有這些重要細節的密碼和配置,這樣我們就不必繼續編輯它們,可以對它們進行單獨的管理。將下列內容儲存在你專案目錄的 secrets.py 中,然後用你的值來替換佔位符值。

# 用你的值替換下面的值
# 你的 API 金鑰和密碼可以在這裡找到 https://dashboard.nexmo.com/getting-started-guide
NEXMO_API_KEY = "<your-api-key>"
NEXMO_API_SECRET = "<your-api-secret>"
# 你的 nexmo 編號
NEXMO_NUMBER = "+447512345678"
# 這可以在你的 Nexmo 應用程式皮膚上找到
NEXMO_APPLICATION_ID = "<nexmo-application-id>"
# 這是設定你的應用程式時下載的私鑰
NEXMO_PRIVATE_KEY = '''-----BEGIN PRIVATE KEY-----
<your-private-key>
-----END PRIVATE KEY-----'''
 
# 你必須註冊一個免費的微軟賬號才能使用微軟語音翻譯 API:http://docs.microsofttranslator.com/speech-translate.html
MICROSOFT_TRANSLATION_SPEECH_CLIENT_SECRET = "<your-api-key>"
複製程式碼

之後,在你的專案目錄中,在 config.py 中儲存以下內容,然後再用值替換佔位符值。注意,你也可以選擇以下語言的其他語言。你也可以在之後的任意時間更改這些內容。

HOSTNAME = '<your-value>.ngrok.io'
 
# 用相同格式的數字替換變數賦值
CALLER = '447812345678'
 
# 用語言替換變數值
LANGUAGE1 = 'de-DE'
 
 
# 將變數賦值替換為你的語言的相應名稱。可以在這裡找到:
# https://developer.nexmo.com/api/voice/ncco#voice-names
VOICE1 = 'Marlene'
 
# 其他語言和語音
LANGUAGE2 = 'en-US'
VOICE2 = 'Kimberly'
複製程式碼

教程步驟

我們將首先介紹如何使用語言翻譯 API 進行身份認證。然後我們將使用提供的模版來設定我們的 Tornado Web 伺服器。之後,我們要實現 CallHandlerEventHandler 以及 WSHandlerCallHandler 將為我們處理 Nexmo number 的呼叫。在此基礎上,EventHandler 將被用於處理 Nexmo 傳送的事件,例如開始或完成的呼叫。在每個事件中,Nexmo 都會傳送關於啟動或完成呼叫的執行者的資訊。我們會使用這些資訊來儲存特定呼叫中的人。WSHandler 同時被用來開啟 WebSocket,Nexmo 和我們的 Python 伺服器通過它進行通訊。Python 伺服器將建立音訊片段並將其傳送到語言翻譯 API。處理器將使用 EventHandler 收集資訊來正確地路由。下面的部分會進一步解釋這些概念,並顯示相應的實現。

使用微軟語言翻譯 API 的身份認證

要使用語音翻譯 API,我們需要一個名為 MICROSOFT_TRANSLATION_SPEECH_CLIENT_SECRET 的 token。幸運的是,微軟提供了一個 Python AzureAuthClient,我們會使用它,不會做任何更改。請將以下內容複製並儲存到你的專案目錄中名為 azure_auth_client.py 的檔案中。

"""
從 Azure 平臺獲取示例 A 的程式碼。
訪問 http://docs.microsofttranslator.com/oauth-token.html 來檢視
微軟 Azure 認知服務的身份驗證服務 API 參考資料。
"""
 
from datetime import timedelta
from datetime import datetime
 
import requests
 
class AzureAuthClient(object):
    """
    Provides a client for obtaining an OAuth token from the authentication service
    for Microsoft Translator in Azure Cognitive Services.
    """
 
    def __init__(self, client_secret):
        """
        :param client_secret: Client secret.
        """
 
        self.client_secret = client_secret
        # token field is used to store the last token obtained from the token service
        # the cached token is re-used until the time specified in reuse_token_until.
        self.token = None
        self.reuse_token_until = None
 
    def get_access_token(self):
        '''
        Returns an access token for the specified subscription.
        This method uses a cache to limit the number of requests to the token service.
        A fresh token can be re-used during its lifetime of 10 minutes. After a successful
        request to the token service, this method caches the access token. Subsequent
        invocations of the method return the cached token for the next 5 minutes. After
        5 minutes, a new token is fetched from the token service and the cache is updated.
        '''
 
        if (self.token is None) or (datetime.utcnow() > self.reuse_token_until):
 
            token_service_url = 'https://api.cognitive.microsoft.com/sts/v1.0/issueToken'
 
            request_headers = {'Ocp-Apim-Subscription-Key': self.client_secret}
 
            response = requests.post(token_service_url, headers=request_headers)
            response.raise_for_status()
 
            self.token = response.content
            self.reuse_token_until = datetime.utcnow() + timedelta(minutes=5)
 
        return self.token
複製程式碼

建立伺服器

計算機通訊協議 WebSocket 允許我們在一個 TCP 連線中擁有一個雙向通訊管道。Nexmo 的 Voice API 允許你將電話呼叫連結到這樣的 WebScoket 端點。我們會使用 Tornado Web 伺服器 web 框架來實現我們的 WebSocket 協議。

如果你一直按照步驟來,而且所有的檔案都如我們所描述的建立,那麼你可以從下面的 Tornado Web 伺服器配置開始。這個程式碼會處理所有的匯入,配置 Nexmo 客戶端以及 azure auth 客戶端,並使用 5000 埠啟動伺服器。注意這個伺服器目前還未執行任何有用操作。它有 3 個端點:nccoeventsocket,它們會分別呼叫 CallHandlerEventHandlerWSHandler。我們會在下面的部分實現處理器。

在你的專案資料夾中建立一個名為 main.py 的檔案,並將以下程式碼複製進去。

from string import Template
import json
import os
import requests
import struct
import StringIO
 
from tornado import httpserver, httpclient, ioloop, web, websocket, gen
from xml.etree import ElementTree
import nexmo
 
from azure_auth_client import AzureAuthClient
from config import HOSTNAME, CALLER, LANGUAGE1, VOICE1, LANGUAGE2, VOICE2
from secrets import NEXMO_APPLICATION_ID, NEXMO_PRIVATE_KEY, MICROSOFT_TRANSLATION_SPEECH_CLIENT_SECRET, NEXMO_NUMBER
 
 
nexmo_client = nexmo.Client(application_id=NEXMO_APPLICATION_ID, private_key=NEXMO_PRIVATE_KEY)
azure_auth_client = AzureAuthClient(MICROSOFT_TRANSLATION_SPEECH_CLIENT_SECRET)
 
conversation_id_by_phone_number = {}
call_id_by_conversation_id = {}
 
 
class CallHandler(web.RequestHandler):
    @web.asynchronous
    def get(self):
        self.write("Hello world")
 
 
class EventHandler(web.RequestHandler):
    @web.asynchronous
    def post(self):
        self.write("Hello world")
 
 
class WSHandler(websocket.WebSocketHandler):
    def open(self):
        print("WebSocket opened")
 
    def on_message(self, message):
        self.write_message(u"You said: " + message)
 
    def on_close(self):
        print("WebSocket closed")
 
 
def main():
    application = web.Application([
        (r"/event", EventHandler),
        (r"/ncco", CallHandler),
        (r"/socket", WSHandler),
    ])
 
    http_server = httpserver.HTTPServer(application)
    port = int(os.environ.get("PORT", 5000))
    http_server.listen(port)
    print("Running on port: " + str(port))
 
    ioloop.IOLoop.instance().start()
 
 
if __name__ == "__main__":
    main()
複製程式碼

實現 CallHandler

為了將電話呼叫連線到 WebSocket 端點,Nexmo 的 Voice API 使用 Nexmo Call Control Object (NCCO) 或 API 呼叫。當有人呼叫你的 Nexmo number 時,Nexmo 就會向你在設定 Nexmo Voice 應用程式時提供的 URL 發起 GET 請求。我們將應用程式指向伺服器,伺服器現在需要通過 NCCO 來響應這個請求。這個 NCCO 應該指示 Nexmo 給呼叫者發生一個簡短的歡迎訊息,然後將呼叫者連線到 WebSocket。

Diagram that shows the interactions between a user, Nexmo, and the web server. When the user calls the Nexmo number, Nexmo sends a GET request to the web server's /ncco endpoint. The web server responds with an NCCO that instructs Nexmo to open a socket with the web server.

顯示使用者、Nexmo 以及 web 伺服器之間的互動圖。當使用者呼叫 Nexmo number 時,Nexmo 就會向 web 伺服器/ncco 傳送一個 GET 請求。web 伺服器會指示 Nexmo 開啟自身的 socket 來讓 NCCO 進行響應。

接著將以下的 NCCO 儲存到你專案中名為 ncco.json 的檔案中。它包含執行請求動作所需的模版。但是,它也包括一些我們以後使用時需要替換的佔位符變數($hostname$whoami$cid)。

[
  {
    "action": "talk",
    "text": "Please wait while we connect you."
  },
  {
    "action": "connect",
    "eventUrl": [
      "http://$hostname/event"
    ],
    "from": "12345",
    "endpoint": [
      {
        "type": "websocket",
        "uri" : "ws://$hostname/socket",
        "content-type": "audio/l16;rate=16000",
        "headers": {
          "whoami": "$whoami",
          "cid": "$cid"
        }
      }
    ]
  }
]
複製程式碼

在伺服器模版中,以下再現部分設定了 /ncco 端點和 CallHandler 之間的對映。這個對映確保了在 /ncco 接收到 GET 請求時,CallHandler 的 get 方法由伺服器執行。

application = web.Application([
    (r"/event", EventHandler),
    (r"/ncco", CallHandler),
    (r"/socket", WSHandler),
])
複製程式碼

當伺服器執行方法時,它會使用以下程式碼返回一個組裝的 NCCO。首先,我們從 data 變數中查詢(即 GET 請求)收集資料。我們還儲存conversation_uuid,以便之後的使用。在這種情況下,有一個列印語句,可以在你測試伺服器時看見 conversation_uuid。接下來,程式碼從我們建立的 ncco.json 檔案中載入 NCCO。為了完成載入 NCCO,我們用從資料變數中手機的值替換佔位符變數($hostname$cid$whoami)。替換之後,我們已經準備好將其返回給 Nexmo 了。

將上述模版中的 CallHandler 替換為以下程式碼:

class CallHandler(web.RequestHandler):
    @web.asynchronous
    def get(self):
        data={}
        data['hostname'] = HOSTNAME
        data['whoami'] = self.get_query_argument('from')
        data['cid'] = self.get_query_argument('conversation_uuid')
        conversation_id_by_phone_number[self.get_query_argument('from')] = self.get_query_argument('conversation_uuid')
        print(conversation_id_by_phone_number)
        filein = open('ncco.json')
        src = Template(filein.read())
        filein.close()
        ncco = json.loads(src.substitute(data))
        self.write(json.dumps(ncco))
        self.set_header("Content-Type", 'application/json; charset="utf-8"')
        self.finish()
複製程式碼

無論何時,只要有人呼叫 Nexmo number,Nexmo 就會向我們的 /ncco 端點傳送 GET 請求,CallHandler 將組裝併傳送 NCCO。Nexmo 之後會執行 NCCO 中所設計的動作。在這種情況下,這意味著呼叫者會聽到**“請稍侯,我們正在與你建立連線。”**之後,Nexmo 會嘗試將呼叫連線到提供的 socket 端點。它也會提供了 Nexmo 要使用的 event 端點。如果你現在通過在終端視窗執行 python main.py 來啟動伺服器,你就會發現你能聽到訊息,但呼叫會在訊息之後結束。這是因為我們沒有實現 EventHandlerWSHandler。我們開始實現吧!

實現 EventHandler

EventHandler 處理 Nexmo 傳送的時間。我們對任何呼叫都感興趣,因此我們會檢查任何請求,以確定其主體是否包含 direction,以及該目錄是否為 incoming。如果是的話,我們會儲存 uuid 並完成上下文請求。call_id_by_conversation_id 字典將用於 WSHandler 中呼叫方之間的訊息路由。

用以下模版程式碼替換 EventHandler

class EventHandler(web.RequestHandler):
    @web.asynchronous
    def post(self):
        body = json.loads(self.request.body)
        if 'direction' in body and body['direction'] == 'inbound':
            if 'uuid' in body and 'conversation_uuid' in body:
                call_id_by_conversation_id[body['conversation_uuid']] = body['uuid']
        self.content_type = 'text/plain'
        self.write('ok')
        self.finish()
複製程式碼

實現 WSHandler

CallHandlerEventHandler 允許我們的應用程式來設定呼叫。WSHandler 現在將關注呼叫的音訊流。語音的主呼叫者將通過語音翻譯 API 轉錄並翻譯,結果文字將由另一端的 Nexmo 語音說出。因此第二個人就可以用他們所明白的語言來傾聽呼叫者的語音了,然後再作出響應。語言翻譯 API 將依次翻譯響應,以便第一格人可以聽到他們的語言。這個工作流就是我們要實現的部分。

當 Nexmo Voice API 連線 WebSocket 時,Nexmo 會向端點傳送一個初始化的 HTTP GET 請求。我們的伺服器響應 HTTP 101 來切換協議,伺服器之後會使用 TCP 連線 Nexmo。連線會通過 Tornado 來為我們處理升級。無論何時有人呼叫 Nexmo number,Nexmo 都會在呼叫期間開啟 WebSocket。當 WebSocket 被開啟並且最後被關閉時,Tornado 框架將呼叫下面的 openclose 方法。我們不需要在這兩種情況下做任何事情,但我們會列印訊息,這樣我們就可以在伺服器執行時跟蹤掌握所發生的一切。

現在我們開啟一個連線,Nexmo 會在 on_message 方法中處理我們傳送的資訊。我們從 Nexmo 收到第一個訊息是帶有後設資料的純文字。在收到這一訊息後,我們會設定 WSHandlerwhoami 屬性,以便能識別發言人。之後,我們會建立一個我們傳送到語音翻譯 API 的 wave 標題。為了向語音翻譯 API 傳送訊息,我們將建立一個 translator_future。根據呼叫者的不同,例如,訊息來源,我們將使用相應的語言變數建立 translator_future,以便 API 瞭解從哪種語言翻譯成哪種其他的語言。

translator_future 是連線到語音翻譯 API 的另一個 WebSocket。我們使用它來傳遞我們從 Nexmo Voice API 接收到的訊息。在它建立之後,translator_future 被儲存在變數 ws 中,被用來傳送我們之前建立的 wave 標題。來自 Nexmo 的每個後續訊息都是二進位制訊息。這些二進位制訊息使用 translator_future 傳遞語音翻譯 API,它會處理音訊並返回轉錄的翻譯。

當我們初始化 translator_future 時,我們宣告語言翻譯 API 處理我們時,它應該會呼叫 speech_to_translation_completed 方法。這個方法在接收到訊息後,會檢查訊息是否為空,然後以訊息接收語言語音出訊息內容。它只會對其他呼叫者說出訊息,而不是最初說話的人。此外,我們還會將翻譯內容列印到終端。

將模版中的 WSHandler 替換為以下程式碼:

class WSHandler(websocket.WebSocketHandler):
    whoami = None
 
    def open(self):
        print("Websocket Call Connected")
 
    def translator_future(self, translate_from, translate_to):
        uri = "wss://dev.microsofttranslator.com/speech/translate?from={0}&to={1}&api-version=1.0".format(translate_from[:2], translate_to)
        request = httpclient.HTTPRequest(uri, headers={
            'Authorization': 'Bearer ' + azure_auth_client.get_access_token(),
        })
        return websocket.websocket_connect(request, on_message_callback=self.speech_to_translation_completed)
 
    def speech_to_translation_completed(self, new_message):
        if new_message == None:
            print("Got None Message")
            return
        msg = json.loads(new_message)
        if msg['translation'] != '':
            print("Translated: " + "'" + msg['recognition'] + "' -> '" + msg['translation'] + "'")
            for key, value in conversation_id_by_phone_number.iteritems():
                if key != self.whoami and value != None:
                    if self.whoami == CALLER:
                        speak(call_id_by_conversation_id[value], msg['translation'], VOICE2)
                    else:
                        speak(call_id_by_conversation_id[value], msg['translation'], VOICE1)
 
    @gen.coroutine
    def on_message(self, message):
        if type(message) == str:
            ws = yield self.ws_future
            ws.write_message(message, binary=True)
        else:
            message = json.loads(message)
            self.whoami = message['whoami']
            print("Sending wav header")
            header = make_wave_header(16000)
 
            if self.whoami == CALLER:
                self.ws_future = self.translator_future(LANGUAGE1, LANGUAGE2)
            else:
                self.ws_future = self.translator_future(LANGUAGE2, LANGUAGE1)
 
            ws = yield self.ws_future
            ws.write_message(header, binary=True)
 
    @gen.coroutine
    def on_close(self):
        print("Websocket Call Disconnected")
複製程式碼

我們使用名為 make_wave_header 的函式來建立語言翻譯 API 所期望的標題。用於建立 WAV 頭的程式碼複製於 Python-Speech-Translate 專案,如下簡介。

make_wave_header 函式複製到 main.py 檔案末尾:

def make_wave_header(frame_rate):
    """
    Generate WAV header that precedes actual audio data sent to the speech translation service.
    :param frame_rate: Sampling frequency (8000 for 8kHz or 16000 for 16kHz).
    :return: binary string
    """
 
    if frame_rate not in [8000, 16000]:
        raise ValueError("Sampling frequency, frame_rate, should be 8000 or 16000.")
 
    nchannels = 1
    bytes_per_sample = 2
 
    output = StringIO.StringIO()
    output.write('RIFF')
    output.write(struct.pack('<L', 0))
    output.write('WAVE')
    output.write('fmt ')
    output.write(struct.pack('<L', 18))
    output.write(struct.pack('<H', 0x0001))
    output.write(struct.pack('<H', nchannels))
    output.write(struct.pack('<L', frame_rate))
    output.write(struct.pack('<L', frame_rate * nchannels * bytes_per_sample))
    output.write(struct.pack('<H', nchannels * bytes_per_sample))
    output.write(struct.pack('<H', bytes_per_sample * 8))
    output.write(struct.pack('<H', 0))
    output.write('data')
    output.write(struct.pack('<L', 0))
 
    data = output.getvalue()
    output.close()
 
    return data
複製程式碼

最後,上述提及的 speak 函式其實是在 nexmo_client 方法 send_speech 周圍的進行的簡單封裝。正如你在下面所看到的那樣,它會列印列印一些在執行程式碼時可能對你有用的資訊,然後使用 Nexmo API 指示 Nexmo 使用給定的 voice_name 來播放 text

將下列 speak 函式複製到你的 main.py 檔案末尾。

def speak(uuid, text, vn):
    print("speaking to: " + uuid  + " " + text)
    response = nexmo_client.send_speech(uuid, text=text, voice_name=vn)
    print(response)
複製程式碼

結論

如果你一直是按照步驟做的,那麼現在應該已經成功構建了自己的 Babel Fish!如果你沒有遵循步驟,也可以在這裡找到原始碼。

通過在終端中輸入 python main.py 來執行。現在和別人合作(或者使用兩部手機)。從兩條線上撥打你的 Nexmo 號碼。你應該可以聽到歡迎資訊,然後就可以用你選擇的兩種語音進行交流了。

我們概括一下:我們首先配置了環境, Nexmo 應用程式和微軟的語言翻譯 API。然後構建了自己的 Tornado WebServer,它允許我們使用 WebSocket 來處理語音呼叫,可以將語音呼叫的語音傳遞給語音翻譯 API。API 為我們翻譯並轉錄語音。得到結果後,我們用新語言說出資訊。我們的路由邏輯使得我們的服務可以處理雙向呼叫,即我們的服務在連線兩個呼叫者後,會先翻譯任何一個人的語音以確保他們彼此可以選擇彼此需要的語言來進行溝通。

我們現在做到了。我們正在執行的 Babel Fish!恐怕我們的 DIY Babel Fish 並不會像電影中的那樣可愛,但這是一種可行性的選擇。

如果你有任何疑問,請聯絡 @naomi_pen 或在 naomi.codes 上找我。

下一步?

如果你對此有深入瞭解的興趣,那麼為什麼不實現允許使用者在呼叫開始時可以選擇語言的邏輯呢?這種邏輯也可能會消除我們硬編碼主要電話號碼的必要性。對於一個有趣的專案來說,你也可以探索為電話會議工作以及為每個電話建立記錄。最後,我設想你可能想要確保你自己服務的安全性以及不讓任何人都有機會呼叫你的服務。你可以通過只允許某個號碼(或多個)來使用你的服務,或者使用第二階段的內部呼叫的邏輯來允許你邀請沒有給定 Bable Fish 服務許可權的使用者。我很想知道你在 Twitter 上構建的內容 —— @naomi_pen

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章