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ライブラリを追加しましょう。

  1. スクリプトエディタを開きます。
  2. メニューより「リソース」⇒「ライブラリ」を開きます。
  3. ライブラリを追加欄に「1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF」を追加します。
  4. 現時点ではバージョンは30が最新ですので、それを選択しておきます。
  5. 保存ボタンを押して完了

これで、OAuth2.0認証にまつわる様々な関数を手軽に利用できるようになります。

図:ライブラリを追加した様子

プロジェクトを移動

今回の発表直前の2019年4月8日より、Google Apps ScriptからCloud Platform Projectへ直接アクセスが出来なくなりました。これまでにデプロイしてるものについては、これまで通り「リソース」⇒「Google Cloud Platform API ダッシュボード」からアクセスが可能です。

今回の変更はスプレッドシート上で動かすスクリプトやGoogleの拡張サービスを利用しないタイプのスクリプトであれば特に問題はありませんが、「Apps Script API」や「Google Picker API」、「Cloud SQL接続」などGCP上のAPIを利用する場合には以下の手順を踏んで、Google Apps Scriptにプロジェクトを連結する必要があります。これまでは、自動的にGCP上にGoogle Apps Script用のプロジェクトが生成されていたのですが、今後は自分の組織(もしくはGCPプロジェクト)上で作成されたプロジェクトでなければならないということです。詳細はこちらのページを見てください。

連結する手順は以下の通り

  1. Google Cloud Consoleを開く
  2. 左上にある▼をクリックする
  3. ダイアログが出てくるので、新規プロジェクトを作るか?既存のプロジェクトを選択する。この時、G Suiteであれば選択元は「自分のドメイン」を選択する必要があります。
  4. プロジェクト情報パネルから「プロジェクト番号」をコピーする
  5. 対象のGoogle Apps Scriptのスクリプトエディタを開く
  6. 「リソース」⇒「Cloud Platform プロジェクト」を開く
  7. 4.で入手した番号をプロジェクトを変更のテキストボックスに入れて、プロジェクトを設定ボタンをクリックする
  8. 無事に移動が完了すればメッセージが表示されます。
  9. この時、元の自動作成されたプロジェクトはシャットダウンされて消えます。これで設定完了です。

今回のこの変更だと1つ作ったプロジェクトに集約する必要があるので、クォータについてプロジェクト毎のカウントだったので問題なかったものが、集約されることで、クォータに引っ掛かる可能性があります。

図:プロジェクト番号をコピーしておきます

図:プロジェクトを他のプロジェクトに紐付けしました。

図:GCPの拡張サービスを使うには手順が必要になった

サービスアカウントの作成

今回のスクリプトの準備で最も面倒なのはこのサービスアカウントの作成です。サービスアカウントの作成自体は以前Google Cloud Consoleを弄ってみるの回で紹介しています。ですが、今回改めてSpeech APIの利用まで含めて特殊なコードが必要なので、ここで紹介いたします。今回はサービスアカウント方式を利用していますが、シンプルなAPI方式もあります(ただし有償課金サービスなので、よりセキュアなこの方式を推奨します)

  1. スクリプトエディタを開き、「リソース」⇒「Googleの拡張サービス」を開く
  2. ダイアログ下にある「Google Cloud Platform API ダッシュボード」を開く
  3. APIとサービスの有効化」をクリックする
  4. Speechで検索し、「Cloud Speech API」をクリックする。Cloud Text-speech APIじゃないので注意!!
  5. 請求の有効化画面が出るので「請求アカウントの作成」をクリック
  6. ウィザードに従い、同意して続行しクレジットカード情報を登録します。
  7. プロジェクトへの認証情報の追加画面では、Cloud Speech APIを選択し、App Engineで使う予定の問いには、「いいえ」で答える
  8. 次のステップではサービスアカウントの名前を入力。わかりやすい名前をつけましょう。キーのタイプはJSONを選択
  9. サービスアカウントの役割では、Project⇒編集者を選択します。
  10. JSONファイルがダウンロードされるので、これを誰とも共有しない形で、Google Driveにアップロードします(Google Cloud Storageも可能です)。流出すると後で課金で痛い目を見るので絶対に共有はしないでください。
  11. アップロードしたJSONファイルの直URLを取得する。https://drive.google.com/open?id=に続けてファイルのIDをつなげればOKです。
  12. 次の項目のJSONキーファイルを取得して認証するにて、冒頭のfilelinkの場所にこのURLを入れてあげる。

図:Cloud Speech APIの有効化画面

図:請求アカウントを作らなければいけません

図:サービスアカウント方式ではなくAPI方式もあります

図:サービスアカウントの権限はできる限り最小で

JSONキーファイルを取得して認証する

//認証用各種変数
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キーの取得に関しては、前回の記事が参考になります。

<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を利用して、テキストに変換されます。

//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で変換してみます。

  1. スプレッドシートのメニューより「文字起こし君」⇒「ファイルの変換」を実行
  2. Pickerが起動するので、FLACファイルを投げ入れてアップロード
  3. しばらく待つ。ファイルのサイズで時間は変わります。
  4. 変換結果のテキストが帰ってきて、メッセージボックスで表示されます。

図:JSONで返ってくる。transcriptが変換結果の入ってる部分です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)