mp4動画からPythonで文字起こししてみた。探せばやり方は山ほどあるので、今回はより実用的に、どんなデータでも使えるよう工夫してみた。
課題
Python で文字起こしするには、recognize_googleを使うのが一般的。ただしこのAPIはファイルサイズが大きすぎるとエラーになる。目安としては5MBくらい。
Speech Recognition Python Having Strange Request Error - Stack Overflow
なので長いファイルは自動で分割したい。ただし、変なところで分割してしまうと、文の途中で切れて文字起こしに悪い影響が出てしまう。そのため、文の切れ目で切っていく。
実装
mp4→wav
recognize_googleではwavファイルしか使用できないので変換する。
参考にさせていただいたサイト様↓
【簡単】Pythonで動画から音声(mp3・wav)を抽出する方法 | ジコログ
FFmpeg とffmpeg-pythonをインストールしたら、以下を実行。
import ffmpeg # 入力 stream = ffmpeg.input("org.mp4") # 出力 stream = ffmpeg.output(stream, "voice.wav") # 実行 ffmpeg.run(stream)
ファイル分割
ファイルを分割していく。この時、「文の切れ目」を「0.1秒間声を出していない時」と考える。まずは声の出ている時と出ていないの閾値を決めるため、音声ファイルをグラフ化する。
参考にさせていただいたサイト様↓
Pythonで音声解析 – 音声データの周波数特性を調べる方法 |じょるブログ
import scipy.io.wavfile import numpy as np import matplotlib.pyplot as plt wav_filename = "voice.wav" rate, data = scipy.io.wavfile.read(wav_filename) #縦軸(振幅)の配列を作成 #16bitの音声ファイルのデータを-1から1に正規化 data = data / 32768
データをプロット
time = np.arange(0, data.shape[0]/rate, 1/rate) plt.plot(time, data[:,0]) plt.xlim(0, 140) plt.show()
このままでは大きすぎてわからないので拡大する。
time = np.arange(0, data.shape[0]/rate, 1/rate) #データプロット plt.plot(time, data[:,0]) plt.xlim(0, 5) plt.ylim(0, 0.25) plt.show()
最小値は0.4あたりなので、余裕を持たせて0.25に設定する。
分割する秒数を取り出す。rangは動画の最低長さ。これがないと細切れになってしまう。 intervalは無言と思われる秒数。
rang = 20 times = [] s_time = -1 e_time = -1 tmp_e_time = -1 interval = 0.1 for t, v in zip(time, data[:,0]): if v > 0.025 and s_time == -1: s_time = t - 0.1 if s_time < 0: s_time = 0 tmp_e_time = -1 if v < 0.025 and s_time != -1 and t - s_time > rang: if tmp_e_time == -1: tmp_e_time = t if t - tmp_e_time > interval : e_time = tmp_e_time + 0.1 if e_time > time[-1]: e_time = time[-1] times.append([s_time, e_time]) s_time = -1 e_time = -1 tmp_e_time = -1 print(len(times)) total =0 for s,e in times: total += e-s print(times[-1], total)
文字起こし
最後に動画を分割しながら文字起こししていく。分割の注意点として、フレーム数はフレームレートの倍数である必要がある。今回は小数点以下の秒数を扱うので、フレームレートの倍数になるようフレームを付け足している。
参考にさせていただいたサイト様↓
【Python】WAVファイルを等間隔に分割するプログラム【サウンドプログラミング】 - ぽきたに 〜ありきたりな非凡〜
Pythonで音声からテキストへ変換【SpeechRecognition】 | ジコログ
import wave import struct import math import os from scipy import fromstring, int16 def cut_wav(filename, times): # timeの単位は[sec] # ファイルを読み出し wavf = filename wr = wave.open(wavf, 'r') # waveファイルが持つ性質を取得 ch = wr.getnchannels() width = wr.getsampwidth() fr = wr.getframerate() fn = wr.getnframes() total_time = 1.0 * fn / fr integer = math.floor(total_time) # 小数点以下切り捨て # waveの実データを取得し、数値化 data = wr.readframes(wr.getnframes()) wr.close() X = fromstring(data, dtype=int16) for i, (s, e) in enumerate(times): frames = int(ch * fr * s-e) # 出力データを生成 outf = 'tmp{}.wav'.format(i) start_cut = int(fr * s) end_cut = int(fr * e) end_cut += (end_cut-start_cut)%fr Y = X[start_cut:end_cut] outd = struct.pack("h" * len(Y), *Y) # 書き出し with wave.open(outf, 'w') as ww: ww.setnchannels(ch) ww.setsampwidth(width) ww.setframerate(fr) ww.writeframes(outd) # 文字起こし r = sr.Recognizer() with sr.AudioFile(outf) as source: audio = r.record(source) text = r.recognize_google(audio, language='ja-JP') print(text) filename = "voice.wav" cut_wav(filename,times)
テスト
実際に動かしてみる。今回はういビーム検出器を作りたかったので以下の動画を使用してテスト。
結果は以下の通り。なぜかういビームだけが検出されてない気がする。。
渡部 君もまた 女バージョンだよ 今ねえよなんで勝手になったら怒られるんだから また目が怒られるんだから 8男性しても悪くないよ 本人より ランドセル 一日一年幸せでいられますように まだ声に宿りし 羽衣の石は現在を持ってきてその辺を示せ 汝この世の不条理を増加するもの何
文字起こし部分がおかしいのか、BGMとかSEのせいでうまくいってないのか、今後調査していく(予定)