2018年10月19日金曜日

Podcast の音源を Google Speech API で文字起こししてみた

とりあえずコマンドだけメモ

  • gsutil cp gs://sound.kakakikikeke.com/ep15.m4a ep15.m4a
  • ffmpeg -i ep15.m4a -f flac -ac1 ep15.flac
  • gsutil cp ep15.flac gs://sound.kakakikikeke.com/
  • gcloud ml speech recognize-long-running 'gs://sound.kakakikikeke.com/ep15.flac' --language-code=ja-JP --async
  • gsutil rm gs://sound.kakakikikeke.com/ep15.flac

概要

現在 Podcast の検索はこのページ内の単語だけを対象に検索することができます
さすがにそれだと検索性が悪すぎるかなーと思い音声情報を文字起こししてそこからエピソードを検索できたらいいなと思いました
ということで Google Speech API を使って文字起こししてみました
なお今回の作業内容はお金が発生するので同じことをするのであれば承知の上作業してください (と行っても数円もかからないですが)

環境

  • macOS 10.14
  • gcloud 220.0.0

flac への変換

自分の podcast は m4a で配信しているのでそれを一旦 flac に変換します
変換してくれる便利なサイトがあるので今回はこれを使わせていただきます
https://online-audio-converter.com/ja/

今回はエピソード14 (Youtube Superchat Ads) の音源を使って文字起こししています

P.S ffmpeg でもできます

  • ffmpeg -i ep14.m4a -f flac ep14.flac

gcloud コマンドのインストール

  • brew cask install google-cloud-sdk

bq, docker-credential-gcloud, gcloud, git-credential-gcloud.sh, gsutil がインストールされます

  • gcloud init

で初期化します
ブラウザが立ち上がり OAuth の認証画面になるので自分の Google アカウントでログインしましょう
あとは使用するプロジェクトやデフォルトリージョンなどを設定します

  • gcloud info

でいろいろと情報を確認できます
詳しい手順はこちらが参考になると思います

文字起こししてみる

  • gcloud ml speech recognize-long-running 'gs://podcast-episodes/ep14.flac' --language-code=ja-JP --async

これで OK です
文章が 1 分以上を越える場合は非同期音声認識を使ったほうが良いみたいです

また音声が何語で話されているのか --language-code で指定する必要があります
日本語も普通に対応していました
サポートしている言語はかなり多いようです

リクエストすると OPERATION_ID が取得できるのでそれを使って進捗を確認することができます

{
  "name": "4268235842997122372"
}
  • gcloud ml speech operations describe 4268235842997122372
{
  "metadata": {
    "@type": "type.googleapis.com/google.cloud.speech.v1.LongRunningRecognizeMetadata",
    "lastUpdateTime": "2018-10-16T03:31:31.157254Z",
    "progressPercent": 5,
    "startTime": "2018-10-16T03:30:28.318655Z"
  },
  "name": "4268235842997122372"
}

progressPercent が 100 になれば完了です

INVALID_ARGUMENT: Request payload size exceeds the limit: 10485760 bytes. 対策

ローカルからファイルをアップロードしてファイルを解析する場合は 10MB 以内でないとダメなようです
その場合はファイルをクラウドストレージに事前にアップロードしてそれを解析するように指定します

今回のファイルサイズは 385329211 bytes あったので全然オーバーしていました
なので Google Cloud Storage にバケットを作成してそこに音声データをアップロードしておきました
バケットは Nearline あたりでいいかなと思います

ちなみに Regionalus-west1 を選択すれば Always free の範囲であれば無料で使えます

INVALID_ARGUMENT: Invalid audio channel count

残念なことに Cloud Speech API はモノラル音源にしか対応していません
kakakikikeke's Podcast はステレオ音源なのでモノラルに変換しました

自分の場合は iTunes で行いました

  • 環境設定 -> 一般 -> 読み込み設定

で設定からカスタムを選択しチャネルを「モノラル」にすれば OK です
podcast_transcript1.png

あとは mp3 音源を選択し AAC で書き出せばモノラルになっています

結果を確認する

  • gcloud ml speech operations describe 4268235842997122372

でテキストが返ってきます
今回のように 60 分を越える音声の場合レスポンスの JSON もかなり長いです

一部抜粋ですが結果は unicode で送られてきます

{
  "alternatives": [
    {
      "confidence": 0.94057757,
      "transcript": "\u3061\u3087\u3063\u3068\u98a8\u90aa\u6c17\u5473\u306a\u3093\u3060\u3093\u307e\u58f0\u304c\u51fa\u306a\u3044\u304b\u3082\u3057\u3093\u306a\u3044\u3042\u306e\u6885\u539f\u5927\u543e\u3063\u3066\u77e5\u3063\u3066\u307e\u3059\u6885\u539f\u5927\u543e\u3055\u3093\u3066\u8ab0\u3067\u3059\u304b\u77e5\u3089\u306a\u3044\u3067\u3059\u304b\u3042\u306e\u30d7\u30ed\u30b2\u30fc\u30de\u30fc\u30d7\u30ed\u683c\u95d8\u5bb6\u30b2\u30fc\u30e0\u30b9\u30c8\u30ea\u30fc\u30c8\u30d5\u30a1\u30a4\u30bf\u30fc\u3068\u304b\u8a00\u3063\u3066\u308b\u3051\u3069\u77e5\u3089\u306a\u3044\u304b\u805e\u3044\u305f\u3053\u3068\u306a\u3044\u3088\u3046\u306a\u81ea\u5206\u7d50\u69cb\u597d\u304d\u3067\u306a\u3093\u304b\u5148\u306b\u30b9\u30dd\u30fc\u30c4\u3068\u304b\u3084\u3063\u3066\u308b\u3088\u306d\u306f\u3044\u306a\u3093\u304b\u3042\u306e\u95a2\u4fc2\u3067\u3042\u306e\u95a2\u4fc2\u3067\u306e\u304a\u83d3\u5b50\u3067\u3059\u672c\u5f53\u306b\u305d\u306e\u30d1\u30a4\u30aa\u30cb\u30a2\u7684\u5b58\u5728\u306e\u4eba\u3067\u30b2\u30fc\u30e0\u3067\u672c\u5f53\u306b\u304a\u91d1\u7a3c\u3044\u3067\u8cde\u91d1\u307f\u305f\u3044\u3067\u3059\u304b\u9577\u3044\u3067\u6709\u540d\u306a\u3084\u3064\u3060\u3068"
    }
  ]
}

Web 上でコンバートしてくれるサービスなどがあるのでそれに投げちゃうのが早いと思います
ちなみに上記の unicode を変換すると以下のようになりました

{
  "alternatives": [
    {
      "confidence": 0.94057757,
      "transcript": "ちょっと風邪気味なんだんま声が出ないかもしんないあの梅原大吾って知ってます梅原大吾さんて誰ですか知らなかあのプロゲーマープロ格闘家ゲームストリートファイターとか言ってるけど知らないか聞いたことないような自分結構好きでなんか先にツとかやってるよねはいなんかあの関係であの関係でのお菓子です本当にそのパイオニア的存在の人でゲームで本当にお金稼いで賞金みたか長いで有名なやつだと"
    }
  ]
}

うーん、何とも絶妙な感じですね、、、
感覚的には confidence が 1.0 から 0.9 の間で更に分解能がある感じで 097 くらいいかないと精度は高くないなーといった印象です

どうやってエピソードを検索するか

やり方はいろいろとあると思います

  • 形態素解析などをして単語に分割し単語検索させる
  • 文章を結合して MySQL などに突っ込んで LIKE 検索させる
  • 検索エンジン (Solr) などを使う

検索機能をどこまで提供するかかなと思います
AND 検索や OR 検索までやりたいのであればデータベースに使うのは必然ですし高速な全文検索をするのであれば検索エンジンを使うでしょう

自分の場合とりあえずある単語を含むエピソードを検索できればいいかなーと思ったのでそのままファイルをメモリ上に展開し正規表現で検索しようかなと思います

require 'json'

def check(keyword)
  json_data = open('./log_jp') do |io|
    JSON.load(io)
  end
  json_data['response']['results'].each { |ret|
    ret['alternatives'].each { |alt|
      return true unless alt['transcript'].match(/#{keyword}/i).nil?
    }
  }
  return false
end

1 エピソード分でしか試してないですが速度も申し分なかったです
これの主なデメリットとしては複雑な検索ができない点ですが、まぁ十分かなと思います
エピソードが増えるとファイルが増えるので検索速度は落ちますが、そこは問題になったら別の仕組みを検討しようと思います

とりあえずこの方法でエピソードを検索できるようにしてみようと思います

おまけ: 今回の作業でおいくらかかったか

今回お金が発生するのは Google Cloud Storage のデータ料金と 60 分以上の音声データを解析した分の料金になります
後日請求が確定したのでコンソール画面で確認したら $0.4 でした
podcast_transcript2.png

podcast_transcript3.png

これまでの全 14 エピソードを文字起こしすると $0.4 * 14 = $5.6 なので激安ですね、、、
それでもお金掛けたくない場合は 60 分未満は無料なので無理やり分割すれば無料でいけるかと思います (Cloud Storage 代はかかると思いますが)

最後に

Podcast の音源を文字起こししてみました
Google Speech API は料金もかなりお安いのでこれで全話文字起こししてみようかなと思います
あとはこのテキスト情報を元に検索できる機能でも実装してみようかなと思います
もしかしたらサポータ限定プログラムにしちゃうかもしれませんが、、
https://kakakikikeke.com/supporter

0 件のコメント:

コメントを投稿