0年 0月 の投稿一覧

Twilioの音声をMicrosoftのBing Speech APIを使ってテキスト化する方法

マインドテックの冨です。

先日、Twilioのハッカソンに行ってきました。電話を使ったアプリケーションを作ろうとすると、音声をテキスト化したいニーズが出てきます。実際、Twilioにはテキスト化の機能もあるのですが、現状では日本語は対象外なんですよね。(あとテキスト化のお値段がちょっと高いw)

そんなわけでテキスト化するためには(自力で実装するのでなければ)外部のAPIを呼び出す必要があります。Google Speech APIを利用する方法は昔、高橋さんがガッツリ書いておられるので、ここではBing Speech APIを利用する方法をご紹介しましょう。サンプルはpython2.7で動作確認をしていますが、もちろん他の言語でも問題ないと思います。

ドキュメントと関連リンク

Bing Speech APIとは
https://azure.microsoft.com/ja-jp/services/cognitive-services/speech/

ドキュメント
https://www.microsoft.com/cognitive-services/en-us/speech-api/documentation/overview

Speech APIではテキスト化する音声認識と、テキストを音声データに変換する機能がありますが、ここでは前者を使います。
Microsoft Congitive Serviceのアカウントは取得しておいてください。
https://www.microsoft.com/cognitive-services/en-us/

APIキーの取得

Cognitive Service の管理画面で”Bing Speech”のサービスの箇所から”key 1″をShow/Copyしておいてください。API呼び出しの際に必要になります。

認証~APIアクセストークンの取得

APIの呼び出しの際には認証が必要になります。詳細はドキュメントを参照いただくとして(笑)キーを送って認証トークンを取得します。トークンは10分間有効。
リクエストヘッダー”Ocp-Apim-Subscription-Key”にAPIキーを指定します。

Pythonでこんな感じに書きました。requestsを使ってます。

import requests
import urllib

def authorize():

    url = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken"

    headers = {
        "Content-type": "application/x-www-form-urlencoded",
        "Ocp-Apim-Subscription-Key": "取得したAPIキー"
    }

    response = requests.post(url, headers=headers)

    if response.ok:
        _body = response.text
        return _body
    else:
        response.raise_for_status()

音声変換

先に取得したアクセストークンは、API呼び出しの際にヘッダーに付与します。

"Authorization": "Bearer " + token

認証の場合と同様に、指定されたエンドポイントにPOSTでデータを投げます。

import requests
import urllib

def speech_to_text( raw_data, token, lang="ja-JP", samplerate=8000, scenarios="ulm"):
    data = raw_data
    params = {
        "version": "3.0",
        "requestid": "b2c95ede-97eb-4c88-81e4-80f32d6aee54",
        "appid": "D4D52672-91D7-4C74-8AD8-42B1D98141A5",
        "format": "json",
        "locale": lang,
        "device.os": "Windows",
        "scenarios": scenarios,
        "instanceid": "565D69FF-E928-4B7E-87DA-9A750B96D9E3" # from Sample Bot Framework
    }
    
    url = "https://speech.platform.bing.com/recognize?" + urllib.urlencode(params)
    headers = {"Content-type": "audio/wav; samplerate={0}".format(samplerate),
               "Authorization": "Bearer " + token }
    
    response = requests.post(url, data=data, headers=headers)
    
    if response.ok:
        result = response.json()["results"][0]
        return result["lexical"]
    else:
        raise response.raise_for_status()

“version”,”appid”などは指定された固定値。
“requestid”,”instanceid”は本当はGUIDを作成しなければいけないようですが、サンプルの文字列で動いたのでそのまま使っています(笑)きちんとしたい人は適当に直してください。
“senario”もulm, websearch などの指定が出来るようですが、違いがよく分かりませんでした。スミマセン。

端末でのテスト

これらを呼び出して実際にテキスト化してみます。適当に声を録音したwavファイルを作成します。(サンプリングレートは8000にしています。)

infile = open("sample.wav", 'r')
raw = infile.read()
txt =  speech_to_text( raw , token, lang="ja-JP", samplerate=8000, scenarios="ulm")
print "text : " + txt

これで

$ python test.py
text : こんにちは

こんな感じで表示されば、呼び出しはうまくいっています。

Twilioの録音音声を上記の例の”sample.wav”に相当するデータとして引き渡しできればOKです。

サーバー側のアプリ

続いて、TwiMLを返し、録音音声を取得して先のSpeech APIを呼び出すWebアプリを作成します。電話口で話した言葉をおうむ返しに読み返してくれます。

ここではpythonのBottleフレームワークを利用して、先に作成した関数も組み込んでみます。

# coding: utf-8
import sys
import os
import bottle
import requests
import urllib
import json
import commands
from bottle import route, run, post, Response, request, static_file
from twilio import twiml
from twilio.rest import TwilioRestClient

reload(sys)
sys.setdefaultencoding('utf-8')

app = bottle.default_app()
twilio_client = TwilioRestClient('TwilioのACCOUNT SID' , 'TwilioのAUTH TOKEN')

TWILIO_NUMBER = os.environ.get('(TWILIO_NUMBER)', '(Twilioで購入した電話番号)')
NGROK_BASE_URL = os.environ.get('NGROK_BASE_URL', '')

@route('/')
def index():
    """Returns standard text response to show app is working."""
    return Response("Bottle app up and running!")

@post('/twiml')
def twiml_response():
    response = twiml.Response()
    
    response.say("何か話してキーを押してください。",language="ja-jp",voice="woman")
    response.record(action="http://(サーバーのアドレス)/handlerecording",method="GET", maxLength="20", finishOnKey="0123456789*#")
    return Response(str(response))

@route('/handlerecording')
def handlerecording():

    recording_url = request.query.get('RecordingUrl')
    voice = urllib.urlopen(recording_url).read()

    ## Authorize for Bing Speech API
    token = authorize()
    txt =  speech_to_text( voice , token, lang="ja-JP", samplerate=8000, scenarios="ulm")
    
    # respond TwiML
    response = twiml.Response()
    response.say(txt,language="ja-jp",voice="woman")

    return Response(str(response))

def authorize():

    url = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken"
    headers = {
        "Content-type": "application/x-www-form-urlencoded",
        "Ocp-Apim-Subscription-Key": "(API key)"
    }
    response = requests.post(url, headers=headers)
    
    if response.ok:
        _body = response.text
        return _body
    else:
        response.raise_for_status()

def speech_to_text( raw_data, token, lang="ja-JP", samplerate=8000, scenarios="ulm"):
    data = raw_data
    params = {
        "version": "3.0",
        "requestid": "b2c95ede-97eb-4c88-81e4-80f32d6aee54",
        "appid": "D4D52672-91D7-4C74-8AD8-42B1D98141A5",
        "format": "json",
        "locale": lang,
        "device.os": "Windows",
        "scenarios": scenarios,
        "instanceid": "565D69FF-E928-4B7E-87DA-9A750B96D9E3" # from Sample Bot Framework
        }

    url = "https://speech.platform.bing.com/recognize?" + urllib.urlencode(params)
    headers = {"Content-type": "audio/wav; samplerate={0}".format(samplerate),
            "Authorization": "Bearer " + token }
        
    response = requests.post(url, data=data, headers=headers)

    if response.ok:
        result = response.json()["results"][0]
        return result["lexical"]
    else:
        raise response.raise_for_status()
        
if __name__ == '__main__':
    run(host='(サーバーのアドレス)', port=80, debug=False, reloader=True)

(Twilioサーバー側の録音音声を削除する機能を入れてませんので、必要に応じて追加してみてください。)

TwiML取得URLの設定

Twilioコンソールで購入した電話番号の、TwiML取得URLを設定します。
電話番号>アクティブな電話番号>音声通話の”A CALL COMES IN”の欄を、上記のBottleアプリが動いているサーバーを指定します。

 

ここまでで作業は終了です。購入した電話番号に電話をかけて、ガイダンスに従って何かを話、任意のボタンを押すと、裏では”Bing Speech API”を呼び出してテキスト化し、それをTwiMLのSay動詞で読み上げを行います。上記のサンプルでお分かりの通り、Twilioの録音音声をそのまま読み込んで、Speech APIに渡せばテキスト化されてきます。特に変換は必要ありませんでした。

取得したテキストは何らかの処理に使えると思いますので、工夫してアプリを作ってみてください。(自分は同じMicrosoftのLUIS(language Understanding Intelligent Service)に送って意味解析をさせる事をやってみました。これについては改めて書こうと思います。)

ではでは。

Azure Passサブスクリプションが突然に無効になって胃が痛くなった件と、その対処方法

マインドテックの冨です。

週末にTwilioハッカソンに出場したのですが、その際にMicrosoft Azureの仮想サーバーを利用させていただきました。
この手のコンテストなどでは、いろいろな会社さんから自社のサービスのトライアル版などの環境をご提供いただけるのですが、Azureについては”Azure Pass”という1か月間有効の1万円分のチャージがされているクーポンのご提供を頂きました。感謝!

で、問題というのは土曜日に四苦八苦しながら開発を進めていて、日曜日に作業を再開しようとしたところ、早朝4時にAzureのアカウントが無効にされていて、管理コンソールにログインはできるものの、仮想サーバーを立ち上げる事も出来なくなったわけです。
さらに悪い事に、無効化を解除する案内も表示されるのですが、ガイドに従ってクレジットカード番号を登録するなどしていくと、最後の最後で「セッションが切れました」というエラー画面が再現しまくりです。おお、神よ・・・

「おお、土曜の作業が壊滅か・・・、今から書き直しても間に合わんなあ・・・」と棄権の予感に胃が痛くなりつつ、マイクロソフトのアテンドの方の到着を待ちわびるわけです。

そしてやっと到着!となって、管理画面を見て頂いたところで「クーポンの残高は残っているのに停止されているとか、通常ありえないですよ」と、なにげなく不穏な空気をにじませつつ、いろいろと調べて頂くも解決せず。
まあ技術エバンジェリストが課金システムの裏とかわからんよねえとか絶望感で胸いっぱい。

何だかんだ、システムからのログイン/ログアウトなどを繰り返していると、無効化解除プロセスを進める事が出来て、何とか仮想サーバーの起動に成功。そしてクーポン残高は10,000円近く残っているも、請求額が1円という不思議なコンソール画面を目撃する事になるのです。

なんだかよくわからないが、開発完了締切が迫るなか、とりあえず作業再開を優先して、課金情報の事はそのまま放置したのです。
で、後日改めてAzureのサポートに連絡。
そして理由が明らかになったのです。それは「Azure内で販売されているサードパーティー製品に対する課金は、Azure Passの対象外」という事です。つまりはMicrosoft社からさらに外部のサードパーティーに支払いが発生するものについてはAzure Passからの支払い対象とはならず、別途に支払いが発生する。そのための支払い情報(クレジットカード番号など)の登録が無い場合には、課金が発生しているが支払い方法が設定されていないアカウントという事で、支払い方法が指定されるまではロックされるというオチでした。

ちなみにAzure Passの対象外となるサービスは以下の通り

・Azure の有償サポートプラン
・サードパーティー製のソフトウェア (仮想マシンのギャラリーから選択可能な Redhat 製品など)
・Azure Marketplace で販売されている製品全般
・Azure とは別に販売されている製品 (Microsoft Azure Active Directory Premium、StorSimple など)

自分に心当たりがあったのは、仮想サーバーのOSとして最初に”Redhat Enterprise Linux(RHEL) 7″を選択したのですが、必要な環境が入れられないとわかり、即座に停止&削除。すぐにUbuntu Linuxの仮想サーバーを作成して作業をしていましたが、RHEL7を起動していたわずかの時間に1円分の課金が発生した事がわかりました。

何をどう考えても回収コストが課金額を大幅に上回る件。大変申し訳ございません。

ああ、そうと分かっていれば最初からUbuntuを選んでいたのにと思いつつ、1円を回収するのに余計にコストがかかって損だろうなあと申し訳なさが混じった、アンビバレントな気分になるわけです。

(ハッカソンの結果は、何とか締切に間に合わせてAzureで動かし、Cognitive APIを何度も呼ぶサービスデモを行ったせいか、「マイクロソフト賞」を頂きました。ありがとうございました。)

もし前世のカルマが災いして、ハッカソンなどでAzureのアカウントが理不尽に止められた場合には、ワタシと同じ目にあっている可能性がありますので、①Azureからログアウト、ブラウザ再起動でログインし直す。②Azure Pass対象外サービスを利用してしまった場合には、クレカ情報を登録する で、リカバれると思います。(クレカを持っていない人は分からん)

ではでは。