Google Apps ScriptとCloud Speech APIで文字起こし【GAS】
Google Cloud Platformには、音声ファイルからテキストを起こすいわゆる「文字起こし」のできるウェブサービスが存在します。それが、Cloud Speech API。これは文字起こしだけではなく、音声認識システムにも利用できるものであり、音声ファイルではなく直接音声をデータ化した後、なげて上げると、Google HomeやAlexaなどにも繋げることのできる、非常に重要なサービスです。
今回はこのサービスを使って、音声データをテキスト化してメッセージボックスで表示するまでを実装してみたいと思います。ただしこのAPIは有料課金APIですので、そのままではGoogle Apps Scriptからは利用できません。課金手続きを行うと現在300ドル分のサービスが無料で利用できますので、その範囲内でやってみたいと思います。
目次
Cloud Speech APIの概要
Cloud Speech APIはGoogle Cloud Platformのサービスの1つです。有料課金サービスであるため、利用する場合には課金登録が必要ですので、クレジットカードが必要です。ですが、初回無料サービスで300ドル分のクーポンが付いてくるので、300ドルまでは無償で利用が可能です(300ドルに到達してもいきなり課金にはならないようです)。
このAPIの利用上の制限は以下の通り。
項目名 | 内容 |
---|---|
音声ファイル | FLAC形式:モノラル サンプリングレート:16000Hz, 16bit
MP3形式 |
無償時間 | 60分/月 |
リクエスト単位 | 15秒単位 |
有償上限時間 | 60分以上で100万分まで |
有償課金 | 0.006ドル/15秒 |
今回使用するファイル・ライブラリ
事前準備
今回のスクリプトは変換元となる音声ファイルが必要です。ただしこのファイルフォーマットは制限事項にもあるように、FLAC形式でモノラルである、など制限を守ったWAVファイルである必要があります。mp3やaacなどは利用ができないので、ICレコーダーなどで取得したファイルなどで、この形式でない場合には、一度変換してあげる必要があります。変換サイトはこちらなどが良いかと。
※2021年現在、ベータ版ですがMP3にも対応したようです。こちらに対応音声ファイルフォーマット一覧があります。エンドポイントもv1p1beta1となり、異なるので注意。
図:事前に音声ファイルを変換しておきましょう
OAuth2.0認証ライブラリの追加
今回のサービスは、OAuth2.0認証が必要です。以下の手順でOAuth2 for Apps Scriptライブラリを追加しましょう。
プロジェクトを移動
サービスアカウントの作成
今回のスクリプトの準備で最も面倒なのはこのサービスアカウントの作成です。サービスアカウントの作成自体は以前Google Cloud Consoleを弄ってみるの回で紹介しています。ですが、今回改めてSpeech APIの利用まで含めて特殊なコードが必要なので、ここで紹介いたします。今回はサービスアカウント方式を利用していますが、シンプルなAPI方式もあります(ただし有償課金サービスなので、よりセキュアなこの方式を推奨します)
- スクリプトエディタを開き、「リソース」⇒「Googleの拡張サービス」を開く
- ダイアログ下にある「Google Cloud Platform API ダッシュボード」を開く
- 「APIとサービスの有効化」をクリックする
- Speechで検索し、「Cloud Speech API」をクリックする。Cloud Text-speech APIじゃないので注意!!
- 請求の有効化画面が出るので「請求アカウントの作成」をクリック
- ウィザードに従い、同意して続行しクレジットカード情報を登録します。
- プロジェクトへの認証情報の追加画面では、Cloud Speech APIを選択し、App Engineで使う予定の問いには、「いいえ」で答える
- 次のステップではサービスアカウントの名前を入力。わかりやすい名前をつけましょう。キーのタイプはJSONを選択。
- サービスアカウントの役割では、Project⇒編集者を選択します。
- JSONファイルがダウンロードされるので、これを誰とも共有しない形で、Google Driveにアップロードします(Google Cloud Storageも可能です)。流出すると後で課金で痛い目を見るので絶対に共有はしないでください。
- アップロードしたJSONファイルの直URLを取得する。https://drive.google.com/open?id=に続けてファイルのIDをつなげればOKです。
- 次の項目のJSONキーファイルを取得して認証するにて、冒頭のfilelinkの場所にこのURLを入れてあげる。
図:Cloud Speech APIの有効化画面
図:請求アカウントを作らなければいけません
図:サービスアカウント方式ではなくAPI方式もあります
図:サービスアカウントの権限はできる限り最小で
JSONキーファイルを取得して認証する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
//認証用各種変数 var tokenurl = "https://accounts.google.com/o/oauth2/token" var filelink = "ここにDriveにアップしたJSON KeyファイルのURLを入力する"; //JSON Keyファイルを指定 //OAuth2認証を実行する function startoauth(){ //UIを取得する var ui = SpreadsheetApp.getUi(); //認証を実行する var service = checkOAuth(); ui.alert("認証が完了し、Access Tokenを取得しました。") } //Google DriveにあるサービスアカウントキーのJSONファイルを取得する function getServiceAccKey(){ //filelinkからFileIDを取得する var fileId = filelink.match(/[\w-]{25,}/)[0]; //JSONファイルの中身を取得する var content = DriveApp.getFileById(fileId).getAs("application/json").getDataAsString(); return JSON.parse(content); } //OAuth2.0認証を実行する function checkOAuth() { //JSONファイルの中身を取得する var privateKeys = getServiceAccKey(); return OAuth2.createService('GoogleCloud:' + Session.getActiveUser().getEmail()) //アクセストークンの取得用URLをセット .setTokenUrl(tokenurl) //プライベートキーとクライアントIDをセットする .setPrivateKey(privateKeys['private_key']) .setIssuer(privateKeys['client_email']) //Access Tokenをスクリプトプロパティにセットする .setPropertyStore(PropertiesService.getScriptProperties()) //スコープを設定する .setScope('https://www.googleapis.com/auth/cloud-platform'); } //Access Tokenを取得する function getOAuthToken() { //DriveAppを参照させておく //https://issuetracker.google.com/issues/131443766 var id = DriveApp.getRootFolder().getId(); //DriveApp.addFile("tst"); //アクセストークンを取得する return ScriptApp.getOAuthToken(); } |
- この認証方式はサービスアカウントが認証を行うので、いつものユーザが認証するものとは異なり、Access Token取得は自動で行われます。
- 取得したアクセストークン等の塊は、OAuth2ライブラリ最新版より、スクリプトプロパティではなくユーザプロパティに格納されているので、より安全になっています。塊は、var service = checkOAuth();で呼び出せます。
Google Picker用の準備
今回のスクリプトでは、ユーザがFLACファイルをそのままアップロードして、変換できるようにPicker APIを利用しています。Picker APIでは単一ファイルをアップロードでき、アップロード後にはconvertAtoT関数へファイルのIDを渡してあげています。APIキーの取得に関しては、前回の記事が参考になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
<link href="https://ssl.gstatic.com/docs/script/css/add-ons.css" rel="stylesheet" /> <script type="text/javascript" src="https://apis.google.com/js/api.js"></script> <script type="text/javascript"> var DEVELOPER_KEY = 'ここにデベロッパーキーを入れる'; var DIALOG_DIMENSIONS = {width: 750, height: 450}; var pickerApiLoaded = false; var origin = google.script.host.origin; var parent = "ここに保存先フォルダのIDを入れる"; //Google Picker API呼び出し gapi.load('picker', {'callback': function() { pickerApiLoaded = true; }}); //ドラッグエリアを表示する google.script.run.withSuccessHandler(createPicker).withFailureHandler(showError).getOAuthToken(); //Picker Dialogを表示する function createPicker(token) { //Pickerを表示 if (pickerApiLoaded && token) { var uploadView = new google.picker.DocsUploadView().setParent(parent); var picker = new google.picker.PickerBuilder() .addView(uploadView) .hideTitleBar() .setOAuthToken(token) .setOrigin(origin) .setLocale("ja") .enableFeature(google.picker.Feature.NAV_HIDDEN) .setDeveloperKey(DEVELOPER_KEY) .setCallback(pickerCallback) .setSize(DIALOG_DIMENSIONS.width - 10, DIALOG_DIMENSIONS.height -10) .build(); picker.setVisible(true); } else { showError('Pickerをロード出来ませんでした。'); } } //Callbackデータを受け取る function pickerCallback(data) { if (data.action == google.picker.Action.PICKED) { var length = data.docs.length; var urlbase; for(var i = 0;i<length;i++){ var fileId = data.docs[i].id; //アップロードファイルのIDを取得して変換ルーチンへ渡す google.script.run.convertAtoT(fileId); } } else if (data.action == google.picker.Action.CANCEL) { google.script.run.messager("アップロードはキャンセルされました。"); } } //エラー表示用 function showError(message) { document.getElementById('result').innerHTML = 'Error: ' + message; } </script> <div id = "result"></div> |
ソースコード
ここでは、変換用のメインコードを表示しています。Google Picker側でファイルのアップロードを行うと、このルーチンに渡され、音声ファイルがAPIを利用して、テキストに変換されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
//FLACファイルをテキストに変換して表示する function convertAtoT(fileid) { var ui = SpreadsheetApp.getUi(); //ファイルの情報と言語の設定 var file = DriveApp.getFileById(fileid); var bytes = file.getBlob().getBytes(); var lang = "ja-jp"; var payload = { config: { //"encoding": "LINEAR16", //"sampleRateHertz": 16000, languageCode: "ja-JP" }, audio: { //音声ファイルをBASE64でエンコードする content:Utilities.base64Encode(bytes) } }; //OAuth認証情報を取得 var service = checkOAuth(); //FLACファイルをAPIへ渡す if (service.hasAccess()) { //Access Tokenを取得 var accessToken = service.getAccessToken(); try{ //POSTで通信 var url = "https://speech.googleapis.com/v1/speech:longrunningrecognize"; var response = UrlFetchApp.fetch(url, { method: 'POST', headers: { Authorization: 'Bearer ' + accessToken }, contentType: "application/json", payload: JSON.stringify(payload) }); //変換結果を取得する var result = JSON.parse(response.getContentText()); //次のUrlFechApp実行まで待ちまでしばらくスリープさせる Utilities.sleep(10 * 1000); //文字起こしデータを取得する url = "https://speech.googleapis.com/v1/operations/" + result.name; var response = UrlFetchApp.fetch(url, { method: 'GET', headers: { Authorization: 'Bearer ' + accessToken } }); //結果をメッセージで表示 var retjson; var result2 = JSON.parse(response.getContentText()); retjson = result2.response.results[0].alternatives[0].transcript ui.alert(retjson); }catch(e){ ui.alert("error:" + e.message); } } } |
- ファイルの変換と結果の取得で2回UrlFetchAppを2回実行しています。
- 其のため、途中で10秒間Sleepを入れる必要があります。連続実行時間制限回避の為です。
- PickerからアップロードしたファイルのIDを取得し、Base64でエンコードしたものをAPIに渡しています。
- 変換結果はretjsonに文字列として帰ってきます。これをメッセージボックスで表示しています。
- 変換結果に句読点はありません。将来的には句読点がつくかもしれません。
- 変換結果のクオリティは上々。
実行結果
実際にサンプルで用意した機械音声のFLACファイルをAPIで変換してみます。
- スプレッドシートのメニューより「文字起こし君」⇒「ファイルの変換」を実行
- Pickerが起動するので、FLACファイルを投げ入れてアップロード
- しばらく待つ。ファイルのサイズで時間は変わります。
- 変換結果のテキストが帰ってきて、メッセージボックスで表示されます。
図:JSONで返ってくる。transcriptが変換結果の入ってる部分です。