LoginSignup
17
2

Amazon Transcribe の「音声の文字起こし」を Livebook から実行する

Last updated at Posted at 2023-12-12

はじめに

Amazon Transcribe は AWS 上で使える AI による音声認識 = 音声の文字起こしサービスです

ブラウザからお手軽に試す場合、以下の記事を参考にしてください

本記事では Elixir の Livebook から Transcribe を使い、音声を録音して認識するまでの一連処理を実装します

本記事での音声認識の流れ

  • Livebook で自分の声を録音する
  • 録音した音声ファイルを AWS 上の S3 バケットにアップロードする
  • S3 上のファイルを対象として音声認識を実行する
  • 音声認識結果をダウンロードする

実装したノートブックはこちら

Livebook とは

Livebook は Elixir のコードをブラウザから実行し、結果を表示してくれるツールです

Python における Jupyter のようなもので、 Elixir 入門や Elixir を使ったデータ分析、データの視覚化などに適しています

はじめ方は以下の記事を参考にしてください

セットアップ

Livebook で新しいノートブックを開いたら、先頭のセットアップセルに以下のコードを入力し、実行してください

Mix.install([
  {:aws, "~> 0.13"},
  {:hackney, "~> 1.20"},
  {:req, "~> 0.4"},
  {:kino, "~> 0.11"}
])

AWS サービスの操作用に AWS Elixir というモジュールをインストールしています

また、音声認識結果をダウンロードするため Req を使います

クライアントの準備

AWS の認証情報を入力するためのテキストボックスを準備します

access_key_id_input = Kino.Input.password("ACCESS_KEY_ID")
secret_access_key_input = Kino.Input.password("SECRET_ACCESS_KEY")
region_input = Kino.Input.text("REGION")

[
  access_key_id_input,
  secret_access_key_input,
  region_input
]
|> Kino.Layout.grid(columns: 3)

セルを実行して表示されたテキストボックスにそれぞれ入力してください

スクリーンショット 2023-12-10 15.13.58.png

AWS の API と通信するためのクライアントを用意します
この際、先ほどの認証情報を受け渡します

client =
  AWS.Client.create(
    Kino.Input.read(access_key_id_input),
    Kino.Input.read(secret_access_key_input),
    Kino.Input.read(region_input)
  )

バケット選択

音声ファイルを置いておく場所として S3 バケットを選択します

自分の AWS アカウントにある S3 バケットの一覧を確認してみましょう

buckets =
  client
  |> AWS.S3.list_buckets()
  |> elem(1)
  |> Map.get("ListAllMyBucketsResult")
  |> Map.get("Buckets")
  |> Map.get("Bucket")

Kino.DataTable.new(buckets)

実行すると、 S3 バケットの取得結果をテーブルとして表示します

もし S3 バケットがない場合は作成しておきましょう

テキストボックスを用意します

s3_bucket_input = Kino.Input.text("S3_BUCKET")

スクリーンショット 2023-12-09 12.54.24.png

使いたい S3 バケットの名前を入力しておきます

音声ファイルアップロード

Kino.Input.audio で音声入力を用意します

このとき、 format: :wav を指定することで、 .wav 形式の音声ファイルとして録音結果を保存できます

audio_input = Kino.Input.audio("Audio", format: :wav)

スクリーンショット 2023-12-09 13.48.11.png

「Record」ボタンをクリックすると、録音が開始されます

初回だけブラウザからマイクの使用許可を求められるため、「許可する」をクリックしてください

スクリーンショット 2023-12-09 13.51.00.png

録音中は音声入力が以下のような状態になります

スクリーンショット 2023-12-09 13.49.48.png

喋る音声は何でもいいのですが、今回は日本国憲法第11条を読んでみました

国民は、すべての基本的人権の享有を妨げられない。この憲法が国民に保障する基本的人権は、侵すことのできない永久の権利として、現在及び将来の国民に与へられる。

〔自由及び権利の保持義務と公共福祉性〕

「Stop recording」をクリックすると、録音が終了します

Livebook で音声を録音した場合、その結果をファイルとして取得できます

一時ファイルとして保存したファイルのパスを取得します

audio_file_path =
  audio_input
  |> Kino.Input.read()
  |> Map.get(:file_ref)
  |> Kino.Input.file_path()

実行結果

"/tmp/livebook/sessions/4aggigzpnkov2utrwlskyao6ph5atqbowoy4njz7la3aw4mx/registered_files/v4qnt3zx2d6uobnzwlyp7wbwqpfi766n"

音声ファイルを S3 にアップロードします

bucket_name = Kino.Input.read(s3_bucket_input)
s3_key = "audio_for_transcribe/input.wav"
body = File.read!(audio_file_path)
md5 = :crypto.hash(:md5, body) |> Base.encode64()

client
|> AWS.S3.put_object(
  bucket_name,
  s3_key,
  %{"Body" => body, "ContentMD5" => md5}
)

音声認識ジョブの実行

アップロード先の S3 バケット、キーを指定して音声認識を実行します

start_transcription_job で音声認識ジョブを開始しましょう

"TranscriptionJobName" は一意である必要があるため、もしもう一回実行する場合、名前を変えるか、先に実行したジョブを削除しておかなければいけません

"LanguageCode" => "ja-JP" により、日本語であることを指定しています

transcription_job =
  client
  |> AWS.Transcribe.start_transcription_job(%{
    "TranscriptionJobName" => "sample-transcription-job",
    "Media" => %{
      "MediaFileUri" => "s3://#{bucket_name}/#{s3_key}"
    },
    "LanguageCode" => "ja-JP"
  })
  |> elem(1)
  |> Map.get("TranscriptionJob")

実行結果

%{
  "CreationTime" => 1702090930.484,
  "LanguageCode" => "ja-JP",
  "Media" => %{"MediaFileUri" => "s3://<バケット名>/<S3キー>"},
  "StartTime" => 1702090930.51,
  "TranscriptionJobName" => "sample-transcription-job",
  "TranscriptionJobStatus" => "IN_PROGRESS"
}

この時点ではジョブを開始しただけで、まだ結果は出ていません

"TranscriptionJobStatus" => "IN_PROGRESS" となっている通り、実行中の状態です

get_transcription_job によって、指定した音声認識ジョブの状況を確認できます

result =
  client
  |> AWS.Transcribe.get_transcription_job(%{
    "TranscriptionJobName" => transcription_job["TranscriptionJobName"]
  })
  |> elem(1)
  |> Map.get("TranscriptionJob")

何回か実行して以下のように "TranscriptionJobStatus" => "COMPLETED" となれば音声認識が完了しています

%{
  "CompletionTime" => 1702090945.618,
  "CreationTime" => 1702090930.484,
  "LanguageCode" => "ja-JP",
  "Media" => %{"MediaFileUri" => "s3://<バケット名>/<S3キー>"},
  "MediaFormat" => "wav",
  "MediaSampleRateHertz" => 48000,
  "Settings" => %{"ChannelIdentification" => false, "ShowAlternatives" => false},
  "StartTime" => 1702090930.51,
  "Transcript" => %{
    "TranscriptFileUri" => "https://s3.ap-northeast-1.amazonaws.com/aws-transcribe-ap-northeast-1-prod/104378222407/sample-transcription-job/40028b98-1cba-4321-8d8c-5e8dc91fd0d8/asrOutput.json?X-Amz-Security-Token=..."
  },
  "TranscriptionJobName" => "sample-transcription-job",
  "TranscriptionJobStatus" => "COMPLETED"
}

音声認識結果の取得

音声認識結果をダウンロードしましょう

transcript_file_uri =
  result
  |> Map.get("Transcript")
  |> Map.get("TranscriptFileUri")

実行結果

"https://s3.ap-northeast-1.amazonaws.com/aws-transcribe-ap-northeast-1-prod/104378222407/sample-transcription-job/40028b98-1cba-4321-8d8c-5e8dc91fd0d8/asrOutput.json?X-Amz-Security-Token=..."

音声認識結果は JSON 形式で、 AWS 管理下の S3 上に保存され、セキュリティトークン付きでダウンロードできるようになっています

音声認識ジョブの開始時、自分の AWS アカウント上の S3 バケットを出力先に指定することも可能です

Req を使って JSON ファイルの内容を取得します

transcript_json =
  transcript_file_uri
  |> Req.get!()
  |> Map.get(:body)

実行結果

%{
  "accountId" => "104378222407",
  "jobName" => "sample-transcription-job",
  "results" => %{
    "items" => [
      %{
        "alternatives" => [%{"confidence" => "0.999", "content" => "国民"}],
        "end_time" => "1.909",
        "start_time" => "1.159",
        "type" => "pronunciation"
      },
      ...
      ],
    "transcripts" => [
      %{
        "transcript" => "国民は全ての基本的人権の共有を妨げられない。この憲法が国民に保障する基本的人権は侵すことのできない永久の権利として、現在および将来の国民に与えられる自由及び権利の保持義務と公共福祉性"
      }
    ]
  },
  "status" => "COMPLETED"
}

音声認識結果からテキスト全文を抜き出します

transcript_json["results"]["transcripts"]
|> Enum.at(0)
|> Map.get("transcript")

実行結果

"国民は全ての基本的人権の共有を妨げられない。この憲法が国民に保障する基本的人権は侵すことのできない永久の権利として、現在および将来の国民に与えられる自由及び権利の保持義務と公共福祉性"

単語単位の認識結果をテーブル表示してみましょう

transcript_json["results"]["items"]
|> Enum.map(fn item ->
  top = Enum.at(item["alternatives"], 0)

  item
  |> Map.put("content", top["content"])
  |> Map.put("confidence", top["confidence"])
  |> Map.put("start_time", Map.get(item, "start_time", nil))
  |> Map.put("end_time", Map.get(item, "end_time", nil))
  |> Map.drop(["alternatives"])
end)
|> Kino.DataTable.new()

実行結果
スクリーンショット 2023-12-09 13.03.27.png

「享有」が「共有」になっているのはご愛嬌

音声認識ジョブの削除

最後に、不要になったものを削除しておきましょう

client
|> AWS.Transcribe.delete_transcription_job(%{
  "TranscriptionJobName" => transcription_job["TranscriptionJobName"]
})

アップロードした音声ファイルも消しておきます

client
|> AWS.S3.delete_object(bucket_name, s3_key, %{})

まとめ

Livebook で録音した音声を Amazon Transcribe で文字起こしできました

Livebook で実装できたということは、同じように Phoenix LiveView で作った画面から音声を録音し、 Amazon Transcribe で文字起こしすることも可能です

もちろん、 Amazon Transcribe を使わずに Bumblebee 等で音声認識することも可能ですが、音声認識の頻度、掛けられるコスト、システム構成などに応じて使い分けましょう

17
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
2