mokajima.com

Cloud Speech-to-Text を使って英語音声ファイルを文字変換する

はじめに

英語のポッドキャストで聞きたいエピソードがありました。聞いてみようと思ったものの、エピソードは 45 分あり、私にとっては長い時間でした。英語は読む方が速いため、文字起こしの方法はないものかと調べていたところ、Google Cloud Platform(以下、GCP) に Speech-to-Text という音声文字変換サービスがあったため、試してみました。

やったこと

音声文字変換を行う Node.js スクリプトを TypeScript で書きました。コードはこちらです。

https://github.com/mokajima/speech-to-text-demo

使い方

$ GOOGLE_APPLICATION_CREDENTIALS=./serviceAccount.json ts-node index.ts -e 'FLAC' -u 'gs://cloud-samples-tests/speech/brooklyn.flac'
$ GOOGLE_APPLICATION_CREDENTIALS=./serviceAccount.json node index.js -e 'FLAC' -u 'gs://cloud-samples-tests/speech/brooklyn.flac'

ts-node index.tsnode index.js を実行します。スクリプトの実行に成功すると、実行結果が transcription.txt というファイル名で出力されます。

環境変数 GOOGLE_APPLICATION_CREDENTIALS に GCP のサービス アカウントキーが含まれる JSON ファイルのパスを指定します。GCP のサービス アカウントキーの取得方法については後述します。

-e--encoding の略で、音声ファイルのエンコードを指定します。-u--uri の略で、音声ファイルの URI を指定します。1 分以上の音声ファイルの文字変換を行いたい場合、音声ファイルを Cloud Storage に置く必要があります。1 そのため、-ugs://バケット名/ファイル名 の形式で指定します。音声ファイルの Cloud Storage への格納方法については後述します。

オプションに -l-s を指定することも可能です。指定可能なオプションの一覧は次の通りです。

オプション オプション
(省略系)
必須 初期値
--encoding -e 音声ファイルのエンコード 2
--uri -u 音声ファイルの URI
--languageCode -l en-US 音声ファイルの言語 3
--sampleRateHertz -s 16000 音声ファイルのサンプルレート

GCP のサービス アカウントキーの取得

GCP のサービス アカウントキーを取得するには、GCP のプロジェクトを作成する必要があります。GCP のコンソールのヘッダーの「プロジェクトの選択」をクリックします。

select-project.png

「プロジェクトの選択」ダイアログの右上の「新しいプロジェクト」をクリックします。

to-new-project.png

「新しいプロジェクト」ページで「作成」をクリックします。「プロジェクト名」「場所」は初期値のままで問題ありません。

new-project.png

プロジェクトの作成が完了すると、プロジェクトのダッシュボードが表示されます。ナビゲーションメニューの「IAM と管理」>「サービス アカウント」をクリックします。

to-service-accounts.png

「プロジェクト『プロジェクト名』のサービス アカウント」ページで「サービス アカウントを作成」をクリックします。

create-service-account.png

「サービス アカウントの詳細」で「サービス アカウント名」と「サービス アカウント ID」を入力し、「作成」をクリックします。

service-account-detail.png

「このサービス アカウントにプロジェクトへのアクセスを許可する (省略可)」でサービス アカウントに付与するアクセス権を指定します。ここでは「ロール」>「Cloud Storage」>「Storage オブジェクト閲覧者」を選択します。後ほど Cloud Storage に音声ファイルを格納するため、Cloud Storage の閲覧権限を付与しておく必要があります。

「Storage オブジェクト閲覧者」を選択したら、「続行」をクリックします。

allowing-service-account-to-access.png

「ユーザーにこのサービス アカウントへのアクセスを許可 (省略可)」で「完了」をクリックします。

allowing-user-to-access.png

サービス アカウントの作成が完了すると、「プロジェクト『プロジェクト名』のサービス アカウント」ページに遷移します。作成したサービスアカウント名(「メール」カラムの値)をクリックします。

projects-service-account.png

「詳細」タブの内容が表示されます。「キー」タブをクリックします。

detail.png

「鍵を追加」>「新しい鍵を作成」をクリックします。

key.png

「『サービス アカウント』の秘密鍵の作成」ダイアログで「キーのタイプ」>「JSON」を選択します。「作成」をクリックすると、秘密鍵をダウンロードします。この JSON ファイルがサービス アカウントキーで、使い方serviceAccount.json です。

create-service-account-key.png

音声ファイルの Cloud Storage への格納

ナビゲーションメニューの「Cloud Storage」をクリックします。

cloud-storage.png

「ブラウザ」ページで「バケットを作成」をクリックします。

create-bucket.png

「バケットに名前を付ける」でバケット名を入力し、「続行」をクリックします。

name-bucket.png

「データの保存場所の選択」で「ロケーション タイプ」>「Region」、「ロケーション」>「asia-northeast1 (東京)」を選択し、「続行」をクリックします。

select-location.png

「データのデフォルトのストレージ クラスを選択する」で「Standard」を選択し、「続行」をクリックします。

select-storage-class.png

「オブジェクトへのアクセスを制御する方法を選択する」で「アクセス制御」>「均一」を選択し、「続行」をクリックします。

access-control.png

「詳細設定(省略可)」で「暗号化」>「Google が管理する暗号鍵」を選択し、「作成」をクリックします。

advanced-setting.png

バケットの作成が完了すると、「バケットの詳細」ページに遷移します。「ファイルをアップロード」をクリックし、音声ファイルをアップロードします。

bucket-detail.png

コード

import { v1p1beta1 } from '@google-cloud/speech' // (1)
import { program } from 'commander' // (2)
import * as fs from 'fs'

type AudioEncoding =
  | 'ENCODING_UNSPECIFIED'
  | 'LINEAR16'
  | 'FLAC'
  | 'MULAW'
  | 'AMR'
  | 'AMR_WB'
  | 'OGG_OPUS'
  | 'SPEEX_WITH_HEADER_BYTE'
  | 'MP3'

const DEFAULT_CONFIG = {
  // 句読点の自動挿入を有効にする
  enableAutomaticPunctuation: true,
}

const client = new v1p1beta1.SpeechClient()

program // (3)
  .option('-l, --languageCode <languageCode>', undefined, 'en-US')
  .option('-s, --sampleRateHertz <sampleRateHertz>', undefined, '16000')
  .requiredOption('-e, --encoding <encoding>')
  .requiredOption('-u, --uri <uri>')
  .action(async () => { // (4)
    const { encoding, languageCode, sampleRateHertz, uri } = program.opts() // (5)
    const audio = { uri }
    const config = {
      ...DEFAULT_CONFIG,
      encoding,
      languageCode,
      sampleRateHertz,
    }

    try {
      const [operation] = await client.longRunningRecognize({ audio, config }) // (6)
      const [response] = await operation.promise() // (7)

      const transcription = response.results // (8)
        ?.map((result) => result.alternatives?.[0].transcript)
        .join('\n')

      fs.writeFile('./transcription.txt', transcription ?? '', (error) => {
        if (error) {
          throw error
        }
      })
    } catch (error) {
      console.error(error)
    }
  })

program.parse(process.argv) // (9)

(1) v1p1beta1 のインポート

公式ドキュメントのサンプルコード 4 では const speech = require('@google-cloud/speech') のようにデフォルトインポートしていますが、ここでは v1p1beta1 をインポートしています。これは、Cloud Speech-to-Text API の v1 が MP3 非対応のため v1p1beta1 を使う必要があるためです。5 6

(2) Commander.js

Commander.js は Node.js で Command Line Interface(以下、CLI)を作成するためのライブラリです。

(3) program

commander からインポートした programCommand クラスのインスタンスです。commander から Command クラスをインポートして、new Command() することも可能です。7

option()requiredOption() はスクリプト実行時にオプションを渡せるようにするためのメソッドです。option() で任意オプションを、requiredOption() で必須オプションを渡せるようにします。

option()requiredOption() の第一引数はフラグ名です。-e, --encoding <encoding>-u, --uri <uri> のように フラグ名 <任意の名前> とすることで、オプションが真偽値以外の値を取ることができます。なお、オプションの値は program.opts() で取得可能ですが、<任意の名前>program.opts() の返り値に影響しません。そのため、.requiredOption('-u, --uri <uri>').requiredOption('-u, --uri <value>') とすることも可能です。

option() の第二引数はオプションの説明文です。第二引数は省略可能ですが、第三引数に値を渡したいため、ここでは undefined を指定しています。

option() の第三引数はオプションの初期値です。

(4) action()

action() に CLI 実行時の処理を記述します。

(5) program.opts()

program.opts() でオプションの値を取得し、client.longRunningRecognize() に渡すための audioconfig を定義しています。

(6) client.longRunningRecognize()

client.longRunningRecognize() は音声文字変換を非同期で実行します。レスポンスはタプル型で、最初の要素が Operation のインスタンスとなっています。分割代入で operation に代入しています。

(7) operation.promise()

operation.promise() は音声文字変換の結果を Promise で返します。レスポンスはタプル型で、最初の要素に文字起こしが含まれます。分割代入で response に代入しています。

たとえば、gs://cloud-samples-tests/speech/brooklyn.flac を音声文字変換してみます。8

$ GOOGLE_APPLICATION_CREDENTIALS=./serviceAccount.json ts-node index.ts -e 'FLAC' -u 'gs://cloud-samples-tests/speech/brooklyn.flac'

このとき response の値は次のようになります。

{
  results: [
    {
      alternatives: [
        {
          transcript: "How old is the Brooklyn Bridge?",
          confidence: 0.9822515845298767,
        },
      ],
      languageCode: "en-us",
    },
  ],
},

(8) transcription.txt へ出力

response.results を加工し、transcription に代入した後、fs.writeFile()transcription.txt として出力しています。

(9) program.parse(process.argv)

program.parse(process.argv) は引数を処理し、オプションによって使われなかった引数を program.args 配列に残します。たとえば、次のコマンドを実行した場合、program.args['foo', 'bar'] となります。

$ GOOGLE_APPLICATION_CREDENTIALS=./serviceAccount.json ts-node index.ts -e 'FLAC' -u 'gs://cloud-samples-tests/speech/brooklyn.flac' foo bar

実行時間

音声ファイルのエンコード 音声ファイルの長さ 実行時間
FLAC 約 10 秒 約 1 分
MP3 約 45 分 約 10 分

費用

GCP のコンソールの「お支払い」を確認したところ、約 45 分の音声ファイルの実行を 2 回、約 10 秒の音声ファイルの実行を何度か行った結果、約 90 円でした 💰

おわりに

GCP の Speech-to-Text を使って音声文字変換を行いました。音声文字変換の精度が高かったため、機会があればまた使ってみたいと思います。日本語音声についても試してみたいところです。

参考