😽

自分でコードを書かずにChatGPTに指示だけしてGoogle speech-to-text APIを使って発表の音声をテキストにしてみた

2023/03/21に公開

動機

最近やった自分の発表をspeech-to-textでどの程度文字起こしができるのかやってみようと思いました。
前回Googleのtext-to-speech APIを試したときに、間違ってspeech-to-text APIも有効にしていたので、これを使ってみることにしました。また、せっかくChatGPTに課金したので、ChatGPTに指示してコードを書いてもらうようにしました。
しかし、あいにくGPT-4 は混みすぎていて応答が返ってこなかったので、しかたなくGPT-3.5を使用しました。

Google speech-to-text API

これです。

https://cloud.google.com/speech-to-text/

Googleのspeech-to-text APIを使って、opusの音声ファイルからテキストを生成するGo言語のプログラムを示してください。足りない情報があれば質問してください。

ChatGPT

はい、GoogleのSpeech-to-Text APIを使用して、opusファイルからテキストを生成するGoプログラムを作成することができます。以下は、そのための基本的な手順です。

Google Cloud ConsoleでSpeech-to-Text APIを有効化し、APIキーを取得します。
ターミナルで go get -u cloud.google.com/go/speech/apiv1 を実行して、必要なGoパッケージをインストールします。
以下のサンプルコードを使用して、APIキー、opusファイルのパス、および音声ファイルのエンコーディングタイプを指定します。
(実際のサンプルコード ..)

最初に出てきたのはドキュメントに載っていそうなファイル名が決め打ちのものでした。

入力となるopusファイルのファイル名と出力するテキストファイルのファイル名をそれぞれコマンドラインオプションから取得するようにしてください。

思う通りのものになるまで何度か指示をする必要がありました。

音声認識の結果をテキストファイルに書き込むようにしてください。そのファイル名もコマンドラインオプションから取得してください。

コンパイルしたら以下のようなエラーになりました。
./oput2text.go:66:15: client.WithAPIKey undefined (type *"cloud.google.com/go/speech/apiv1".Client has no field or method WithAPIKey)

APIKeyはデフォルトの環境変数'GOOGLE_APPLICATION_CREDENTIALS' に入っていることを前提としてプログラムを修正してください。

とりあえず、これで試してみようということにしたプログラムはこちら。

opus2text.go
package main

import (
	"context"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"cloud.google.com/go/speech/apiv1"
	speechpb "google.golang.org/genproto/googleapis/cloud/speech/v1"
	"google.golang.org/api/option"
)

func main() {
	// Parse command line flags
	inputFilePath := flag.String("input", "", "Path to the input opus file")
	outputFilePath := flag.String("output", "", "Path to the output text file")
	flag.Parse()

	// Check if input and output file paths are provided
	if *inputFilePath == "" {
		fmt.Println("Input file path is required.")
		flag.Usage()
		os.Exit(1)
	}
	if *outputFilePath == "" {
		fmt.Println("Output file path is required.")
		flag.Usage()
		os.Exit(1)
	}

	// Set up the Google Cloud Speech-to-Text API client
	ctx := context.Background()
	client, err := speech.NewClient(ctx, option.WithCredentialsFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")))
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}

	// Load the audio file into memory
	opusData, err := ioutil.ReadFile(*inputFilePath)
	if err != nil {
		log.Fatalf("Failed to read file: %v", err)
	}

	// Set the audio configuration
	config := &speechpb.RecognitionConfig{
		Encoding:        speechpb.RecognitionConfig_OGG_OPUS,
		SampleRateHertz: 16000,
		LanguageCode:    "ja-JP",
	}

	// Perform the speech recognition
	audio := &speechpb.RecognitionAudio{
		AudioSource: &speechpb.RecognitionAudio_Content{Content: opusData},
	}
	resp, err := client.Recognize(ctx, &speechpb.RecognizeRequest{
		Config: config,
		Audio:  audio,
	})
	if err != nil {
		log.Fatalf("Failed to recognize speech: %v", err)
	}

	// Write the transcription results to the output file
	outputFile, err := os.Create(*outputFilePath)
	if err != nil {
		log.Fatalf("Failed to create output file: %v", err)
	}
	defer outputFile.Close()

	for _, result := range resp.Results {
		for _, alt := range result.Alternatives {
			_, err := fmt.Fprintf(outputFile, "%v\n", alt.Transcript)
			if err != nil {
				log.Fatalf("Failed to write to output file: %v", err)
			}
		}
	}
	fmt.Printf("Transcription complete. Results written to %v\n", *outputFilePath)
}

ついでにffmpegの使い方を聞いてみた

ありがとう。コンパイルできました。
次に、mp4の動画ファイルから音声だけを取り出して、サンプリングレート16kHzモノラルのopusファイルを出力するffmpeg の使い方を示してください。

ChatGPT

音声を抽出するには、FFmpegの -i オプションを使用して入力ファイルを指定し、 -vn オプションを使用してビデオストリームを無効にします。 -ac オプションと -ar オプションを使用して、出力ファイルのサンプリングレートとチャンネル数を指定できます。opusエンコードを使用するには、出力ファイルの拡張子を ".opus" に指定する必要があります。

以下は、FFmpegを使用してmp4ファイルから音声を抽出してopusファイルとして保存するコマンドラインの例です。

ffmpeg -i input.mp4 -vn -ac 1 -ar 16000 output.opus

このコマンドでは、input.mp4 という名前の入力ファイルを指定し、 -vn オプションを使用してビデオストリームを無効にし、-ac 1 オプションと -ar 16000 オプションを使用して、出力ファイルのチャンネル数を1に、サンプリングレートを16kHzに指定しています。また、出力ファイルの拡張子を .opus に指定しています。

これは楽だ。ffmpegのオプションはこれからはChatGPTに聞くことにしよう。
しかし、調子良くいったのはここまででした。

長い音声ファイルへの対応でつまずく

さきほどのspeech-to-textを使うプログラムですが、実行したところ以下のようなエラーになりました。
Failed to recognize speech: rpc error: code = InvalidArgument desc = Sync input too long. For audio longer than 1 min use LongRunningRecognize with a 'uri' parameter.

LongRunningRecognize を使うようにプログラムを修正し、その差分をdiff -u 形式で示してください。

ChatGPT

エラーメッセージにもあるように、Speech-to-Text APIは、60秒以上の長さの音声データに対しては、 LongRunningRecognize メソッドを使用することを推奨しています。これにより、APIは長い音声ファイルを非同期で処理し、operation オブジェクトを返します。 operation オブジェクトには、ジョブの状態を確認するためのURIが含まれています。最終的な結果は、ジョブが完了した後に取得できます。

以下は、 LongRunningRecognize メソッドを使用して音声ファイルを認識する修正後のコードです。修正の差分を diff -u コマンドで示します。

修正前:
(省略)
修正後:
(省略)

変更がけっこう大きかったので、もういちど全部だしてもらいました。
ところが、これがコンパイルエラーになり、それを指摘して修正してもらうとまた別の箇所がコンパイルエラーに。それがモグラたたき状態になってしまいました。
長い音声はストリーミング的に処理する必要があるので、複雑になるのですがこれがなかなかうまくいかない。ChatGPTの知識のないところに入り込んでしまったようです。
方針転換しよう。

音声ファイルを無音のところで分割する

一度に処理できるのが60秒未満ということなので、それに合わせて音声ファイルを分割することにしました。
そのようなことをffmpegを使ってできるかをChatGPTに聞いてみた。
その通りにやってみましたが、オプションの組み合わせが悪いためにエラーになりました。
しかし、そこで提示されたオプションをもとに自分で検索して調べてみると以下のプログラムを見つけました。

https://gist.github.com/vi/2fe3eb63383fcfdad7483ac7c97e9deb

これの使用例

$ ./split_by_silence.sh input_long.opus output%03d.opus

うまくいったと思ったのですが、ちょうど外に救急車が通ったときにそのサイレンの音のために分割されずに60秒を超えてしまうところが一箇所だけありました。

無音の判定の条件を変えて再チャレンジ。

$ SD_PARAMS="-40dB:d=0.2" ./split_by_silence.sh input_long.opus output%03d.opus

今度はうまく分割できました。

とりあえず発表の音声をテキストにすることはできた

さて、ここまでの材料を組み合わせることで、13分くらいの発表の音声をテキストにすることはできました。
Google speech-to-text APIのドキュメントを全く読まずに実現することができました。全部思う通りにできたわけではありませんが、要件に合わせて方向転換することも大事ですよね。

Google speech-to-text APIの出力した結果としては、ちょっと期待よりは下でした。
人がかなり修正する必要がある感じでした。まずは句読点が一切は入っていない。自分の発表なので、自分の思う通りに書き直せばよいのですが、正確な文字起こしには程遠い感じです。

ちょっと他のspeech-to-text APIも試してみようと思います。

関連

https://zenn.dev/tetsu_koba/articles/f54ae2cf4a18fb

Discussion