ウェブサイト検索

Python、OpenAI Whisper、およびFFmpegを使用して動画に字幕を生成して追加する方法


著者はWrite for DOnationsプログラムの一環として、寄付を受け取るためにオープンソースイニシアティブを選択しました。

紹介

このチュートリアルでは、入力動画から音声を抽出し、抽出した音声を文字起こしし、その文字起こしに基づいて字幕ファイルを作成し、最後にその字幕を入力動画のコピーに追加できるPythonアプリケーションを構築します。

このアプリケーションを構築するには、FFmpegを使用して入力動画から音声を抽出します。その抽出した音声の文字起こしを生成するためにOpenAIのWhisperを使用し、その後この文字起こしを使って字幕ファイルを生成します。さらに、FFmpegを使用して生成した字幕ファイルを入力動画のコピーに追加します。

FFmpegは、音声およびビデオ処理タスクを含むマルチメディアデータを扱うための強力でオープンソースのソフトウェアスイートです。ユーザーがさまざまなフォーマットやコーデックを使用してマルチメディアファイルを変換、編集、操作できるコマンドラインツールを提供します。

OpenAIのWhisperは、自動音声認識(ASR)システムで、話された言語を文字に変換するように設計されています。多言語で多様なタスクの監督データを大量に学習しており、高い精度で多様な音声コンテンツを文字起こしするのに優れています。

このチュートリアルの終わりまでに、あなたはビデオに字幕を追加できるアプリケーションを持つことになります:

前提条件

このチュートリアルをフォローするために、読者は次のツールが必要です。

  • Python バージョン 3.9 以上およびそれに対応する venv がインストールされている

  • FFmpegがインストールされました。

  • Pythonの基本的な理解。これらのチュートリアルシリーズに従って、Pythonでコーディングする方法を学ぶことができます。

ステップ 1 — プロジェクトルートディレクトリの作成

このセクションでは、プロジェクトディレクトリを作成し、入力動画をダウンロードし、仮想環境を作成してアクティブにし、必要なPythonパッケージをインストールします。

ターミナルウィンドウを開き、プロジェクトに適した場所に移動します。次のコマンドを実行してプロジェクトディレクトリを作成します:

  1. mkdir generate-subtitle

プロジェクトディレクトリに移動します:

  1. cd generate-subtitle

この編集されたビデオをダウンロードし、プロジェクトのルートディレクトリにinput.mp4として保存してください。このビデオは、ラシャーンという子供がジャーメイン・エドワーズの「ビューティフルデイ」を歌っている様子を紹介しています。このチュートリアルで使用する編集済みビデオは、以下のYouTubeビデオから取得されたものです。

新しい仮想環境を作成し、それにenvという名前を付けます。

  1. python3 -m venv env

仮想環境をアクティブにします:

  1. source env/bin/activate

さて、このアプリケーションを構築するために必要なパッケージをインストールするには、次のコマンドを使用してください:

  1. pip3 install faster-whisper ffmpeg-python

上記のコマンドで、以下のライブラリがインストールされました:

  • faster-whisper: は、OpenAIのWhisperモデルの再設計版で、Transformerモデル用の高性能推論エンジンであるCTranslate2を活用しています。この実装は、比較可能な精度を維持しながら、openai/whisperと比べて最大4倍の高速化を実現し、メモリの消費も抑えています。

  • ffmpeg-python: は、FFmpegツールの周りにラッパーを提供するPythonライブラリで、ユーザーがPythonスクリプトでFFmpegの機能と簡単に対話できるようにします。Pythonicインターフェースを通じて、編集、変換、操作などのビデオおよびオーディオ処理タスクを可能にします。

pipを使用して仮想環境にインストールしたパッケージをrequirements.txtという名前のファイルに保存するには、次のコマンドを実行してください:

  1. pip3 freeze > requirements.txt

requirements.txtファイルは次のような見た目になるべきです:

av==10.0.0
certifi==2023.7.22
charset-normalizer==3.3.2
coloredlogs==15.0.1
ctranslate2==3.20.0
faster-whisper==0.9.0
ffmpeg-python==0.2.0
filelock==3.13.1
flatbuffers==23.5.26
fsspec==2023.10.0
future==0.18.3
huggingface-hub==0.17.3
humanfriendly==10.0
idna==3.4
mpmath==1.3.0
numpy==1.26.1
onnxruntime==1.16.1
packaging==23.2
protobuf==4.25.0
PyYAML==6.0.1
requests==2.31.0
sympy==1.12
tokenizers==0.14.1
tqdm==4.66.1
typing_extensions==4.8.0
urllib3==2.0.7

このセクションでは、プロジェクトディレクトリを作成し、このチュートリアルで使用する入力動画をダウンロードし、仮想環境を設定して、それをアクティブにし、必要なPythonパッケージをインストールしました。次のセクションでは、入力動画のトランスクリプトを生成します。

ステップ2 — ビデオのトランスクリプトを生成する

このセクションでは、アプリケーションが存在するPythonスクリプトを作成します。このスクリプト内では、ffmpeg-pythonライブラリを使用して、前のセクションでダウンロードした入力ビデオからオーディオトラックを抽出し、それをWAVファイルとして保存します。次に、faster-whisperライブラリを使用して、抽出したオーディオのトランスクリプトを生成します。

プロジェクトのルートディレクトリに main.py という名前のファイルを作成し、以下のコードを追加します:

import time
import math
import ffmpeg

from faster_whisper import WhisperModel

input_video = "input.mp4"
input_video_name = input_video.replace(".mp4", "")

ここでは、コードがさまざまなライブラリとモジュールをインポートすることから始まります。これには、time、math、ffmpeg(ffmpeg-pythonから)、およびfaster_whisperからのカスタムモジュールWhisperModelが含まれています。これらのライブラリは、ビデオとオーディオの処理、転写、および字幕の生成に使用されます。

次に、コードは入力動画ファイル名を設定し、それを input_video という定数に保存します。次に、動画ファイル名から .mp4 拡張子を除いたものを input_video_name という定数に保存します。ここで入力ファイル名を設定することで、生成された字幕および出力動画ファイルを上書きすることなく、複数の入力動画で作業できるようになります。

あなたの main.py の下部に次のコードを追加してください:

def extract_audio():
    extracted_audio = f"audio-{input_video_name}.wav"
    stream = ffmpeg.input(input_video)
    stream = ffmpeg.output(stream, extracted_audio)
    ffmpeg.run(stream, overwrite_output=True)
    return extracted_audio

上記のコードは、入力ビデオから音声を抽出することを担当する extract_audio() という名前の関数を定義しています。

まず、抽出されるオーディオの名前を、入力ビデオのベース名に audio- を追加し、.wav 拡張子を付けた名前に設定し、この名前を extracted_audio という定数に保存します。

次に、コードは ffmpeg ライブラリの ffmpeg.input() メソッドを呼び出して入力動画を開き、stream という名前の入力ストリームオブジェクトを作成します。

その後、コードはffmpeg.output()メソッドを呼び出して、入力ストリームと定義された抽出された音声ファイル名を使用して出力ストリームオブジェクトを作成します。

出力ストリームを設定した後、コードは ffmpeg.run() メソッドを呼び出し、出力ストリームをパラメータとして渡して音声抽出プロセスを開始し、抽出された音声ファイルをプロジェクトのルートディレクトリに保存します。さらに、ブール値パラメータ overwrite_output=True が含まれており、既に存在する出力ファイルがあれば、新しく生成されたファイルで置き換えることができます。

最後に、コードは抽出された音声ファイルの名前を返します。

extract_audio()関数の下に次のコードを追加してください:

def run():

    extracted_audio = extract_audio()
run()

ここで、コードはrun()という名前の関数を定義し、それを呼び出します。この関数は、ビデオに字幕を生成して追加するために必要なすべての関数を呼び出します。

関数内で、コードは extract_audio() 関数を呼び出してビデオから音声を抽出し、返された音声ファイル名を extracted_audio という変数に格納します。

ターミナルに戻り、次のコマンドを実行してmain.pyスクリプトを実行してください:

  1. python3 main.py

上記のコマンドを実行すると、FFmpegの出力がターミナルに表示され、入力動画から抽出された音声を含む audio-input.wav という名前のファイルがプロジェクトのルートディレクトリに保存されます。

自分のmain.pyファイルに戻り、extract_audio()関数とrun()関数の間に次のコードを追加してください:

def transcribe(audio):
    model = WhisperModel("small")
    segments, info = model.transcribe(audio)
    language = info[0]
    print("Transcription language", info[0])
    segments = list(segments)
    for segment in segments:
        # print(segment)
        print("[%.2fs -> %.2fs] %s" %
              (segment.start, segment.end, segment.text))
    return language, segments

上記のコードは、入力ビデオから抽出された音声ファイルを文字起こしするためのtranscribeという名前の関数を定義しています。

まず、コードはWhisperModelオブジェクトのインスタンスを作成し、モデルタイプをsmallに設定します。OpenAIのWhisperには次のモデルタイプがあります:tiny、base、small、medium、largeです。tinyモデルは最も小さく、最も速いものであり、largeモデルは最も大きく、最も遅いですが、最も正確です。

次に、コードは抽出されたオーディオを引数として model.transcribe() メソッドを呼び出し、セグメント関数とオーディオ情報を取得し、それらをそれぞれ info と segments という変数に格納します。セグメント関数はPythonのジェネレーターであるため、コードがそれを反復するまで転写は開始されません。セグメントを list や for ループで集めることで、転写を完了することができます。

次に、コードは音声で検出された言語をinfoという名前の定数に格納し、それをコンソールに表示します。

言語が検出された後、コードは転写セグメントを list に収集して転写を実行し、収集したセグメントを同じ名前の変数 segments に保存します。次に、コードは転写セグメントのリストをループ処理し、各セグメントの開始時間、終了時間、およびテキストをコンソールに印刷します。

最後に、コードは音声で検出された言語と書き起こしセグメントを返します。

run()関数の中に以下のコードを追加してください:

def run():

    extracted_audio = extract_audio()

    language, segments = transcribe(audio=extracted_audio)

追加されたコードは、抽出された音声を引数として transcribe 関数を呼び出し、返された値を language および segments という定数に格納します。

ターミナルに戻り、次のコマンドを実行して main.py スクリプトを実行してください:

  1. python3 main.py

このスクリプトを初めて実行すると、コードはまずWhisper Smallモデルをダウンロードしてキャッシュします。その後の実行ははるかに速くなります。

上記のコマンドを実行した後、コンソールに次の出力が表示されるはずです:

…
Transcription language en
[0.00s -> 4.00s]  This morning I wake up and I look in the mirror
[4.00s -> 8.00s]  Every part of my body was in the place many people lie
[8.00s -> 11.00s]  I don't wanna act too high and mighty
[11.00s -> 15.00s]  Cause tomorrow I may fall down on my face
[15.00s -> 17.00s]  Lord I thank You for sunshine
[17.00s -> 19.00s]  Thank You for rain
[19.00s -> 20.00s]  Thank You for joy
[20.00s -> 22.00s]  Thank You for pain
[22.00s -> 25.00s]  It's a beautiful day
[25.00s -> 28.00s]  It's a beautiful day

上記の出力は、音声で検出された言語が英語であることを示しています (en)。さらに、各転写セグメントの開始時間と終了時間(秒単位)およびテキストも示しています。

警告: OpenAIのWhisper音声認識は非常に正確ですが、100%の正確性はありません。特に難しい言語的または音声的状況において制限や偶発的な誤りがある場合があります。したがって、必ず手動でトランスクリプションを確認してください。

このセクションでは、アプリケーション用のPythonスクリプトを作成しました。スクリプト内では、ffmpeg-pythonを使用してダウンロードした動画から音声を抽出し、WAVファイルとして保存しました。その後、faster-whisperライブラリを使用して抽出した音声の文字起こしを生成しました。次のセクションでは、文字起こしに基づいて字幕ファイルを生成し、その後字幕を動画に追加します。

ステップ3 — 動画に字幕を生成して追加する

このセクションでは、まず、字幕ファイルとは何か、そしてその構造について理解します。次に、前のセクションで生成されたトランスクリプションセグメントを使用して、字幕ファイルを作成します。字幕ファイルを作成した後、ffmpeg-pythonライブラリを使用して、入力ビデオのコピーに字幕ファイルを追加します。

字幕の理解:構造とタイプ

字幕ファイルとは、ビデオや映画の中で話されたり書かれたりした内容に対応するタイム付きのテキスト情報を含むテキストファイルです。通常、各字幕が画面に表示されたり消えたりするタイミングに関する情報が含まれています。多くの字幕フォーマットがありますが、このチュートリアルでは、広く使用されているSubRip(SRT)というフォーマットに焦点を当てます。

字幕ファイルは一連の字幕エントリで構成されており、各エントリは通常、特定の形式に従っています。字幕エントリの一般的な構造には次のものが含まれます:

  1. 字幕インデックス:ファイル内の字幕の順序を示す連番。

  2. タイムコード: 字幕テキストを表示する時間を指定する開始時刻と終了時刻のマーカー。タイムコードは通常、HH:MM:SS,sss(時、分、秒、ミリ秒)の形式でフォーマットされます。

  3. サブタイトルのテキスト:サブタイトルエントリの実際のテキストで、話されたり書かれたりする内容を表しています。このテキストは、指定された時間間隔中に画面に表示されます。

例えば、SRTファイルの字幕エントリは次のように見えるかもしれません:

1
00:00:10,500 --> 00:00:15,000
This is an example subtitle.

この例では、インデックスは1で、タイムコードは字幕が10.5秒から15秒まで表示されることを示しており、字幕テキストはこれは例の字幕です。です。

字幕は主に二つのタイプに分けることができます:

  • ソフト字幕:クローズドキャプションとも呼ばれ、外部ファイル(SRTなど)として別途保存されており、動画とは独立して追加または削除できます。これにより視聴者の柔軟性が提供され、切り替え、言語の変更、設定のカスタマイズが可能です。ただし、その効果は動画プレーヤーのサポートに依存し、すべてのプレーヤーがソフト字幕に普遍的に対応しているわけではありません。

  • ハード字幕:編集またはエンコーディング中にビデオフレームに永久に埋め込まれ、ビデオの固定部分として残ります。これにより、外部字幕ファイルのサポートがないプレーヤーでも常に視覚的に表示されますが、変更やオフにするにはビデオ全体の再エンコーディングが必要になるため、ユーザーのコントロールが制限されます。

字幕ファイルを作成する

あなたのmain.pyファイルに戻り、transcribe()関数とrun()関数の間に次のコードを追加してください:

def format_time(seconds):

    hours = math.floor(seconds / 3600)
    seconds %= 3600
    minutes = math.floor(seconds / 60)
    seconds %= 60
    milliseconds = round((seconds - math.floor(seconds)) * 1000)
    seconds = math.floor(seconds)
    formatted_time = f"{hours:02d}:{minutes:02d}:{seconds:01d},{milliseconds:03d}"

    return formatted_time

ここでは、コードが format_time() という名前の関数を定義しています。この関数は、与えられた転写セグメントの開始時間と終了時間を秒単位で、時間、分、秒、ミリ秒を表示する字幕互換の時間形式 (HH:MM:SS,sss) に変換する役割を担っています。

このコードは、与えられた秒数から時間、分、秒、ミリ秒を最初に計算し、それらを適切にフォーマットし、フォーマットされた時間を返します。

format_time()関数とrun()関数の間に以下のコードを追加してください:

def generate_subtitle_file(language, segments):

    subtitle_file = f"sub-{input_video_name}.{language}.srt"
    text = ""
    for index, segment in enumerate(segments):
        segment_start = format_time(segment.start)
        segment_end = format_time(segment.end)
        text += f"{str(index+1)} \n"
        text += f"{segment_start} --> {segment_end} \n"
        text += f"{segment.text} \n"
        text += "\n"
        
    f = open(subtitle_file, "w")
    f.write(text)
    f.close()

    return subtitle_file

追加されたコードは、抽出された音声で検出された言語と文字起こしセグメントをパラメーターとして取る generate_subtitle_file() という名前の関数を定義しています。この関数は、言語と文字起こしセグメントに基づいてSRT字幕ファイルを生成する役割を担っています。

まず、コードは、字幕ファイルの名前を検出された言語と入力ビデオのベース名に sub- を付加し、“.srt ” 拡張子を付けた名前に設定し、この名前を subtitle_file という定数に保存します。さらに、コードは字幕エントリを保存するために text という名前の変数を定義します。

次に、コードは書き取られたセグメントを繰り返し処理し、format_time()関数を使って開始時刻と終了時刻をフォーマットし、これらのフォーマットされた値をセグメントのインデックスとテキストとともに使用して字幕エントリを作成し、その後に各字幕エントリを区切るために空行を追加します。

最後に、コードはプロジェクトのルートディレクトリに以前設定した名前の字幕ファイルを作成し、そのファイルに字幕エントリを追加し、字幕ファイルの名前を返します。

あなたの run() 関数の最後に次のコードを追加してください:

def run():

    extracted_audio = extract_audio()
    language, segments = transcribe(audio=extracted_audio)
    subtitle_file = generate_subtitle_file(
    language=language,
    segments=segments
    )

追加されたコードは、検出された言語とトランスクリプションセグメントを引数として、generate_subtitle_file()関数を呼び出し、返された名前の字幕ファイルをsubtitle_fileという定数に保存します。

ターミナルに戻り、次のコマンドを実行して main.py スクリプトを実行してください:

  1. python3 main.py

上記のコマンドを実行した後、sub-input.en.srt という名前の字幕ファイルがプロジェクトのルートディレクトリに保存されます。

sub-input.en.srt字幕ファイルを開くと、次のようなものが表示されるはずです:

1
00:00:0,000 --> 00:00:4,000
 This morning I wake up and I look in the mirror

2
00:00:4,000 --> 00:00:8,000
 Every part of my body was in the place many people lie

3
00:00:8,000 --> 00:00:11,000
 I don't wanna act too high and mighty

4
00:00:11,000 --> 00:00:15,000
 Cause tomorrow I may fall down on my face

5
00:00:15,000 --> 00:00:17,000
 Lord I thank You for sunshine

6
00:00:17,000 --> 00:00:19,000
 Thank You for rain

7
00:00:19,000 --> 00:00:20,000
 Thank You for joy

8
00:00:20,000 --> 00:00:22,000
 Thank You for pain
 
9
00:00:22,000 --> 00:00:25,000
 It's a beautiful day

10
00:00:25,000 --> 00:00:28,000
 It's a beautiful day

動画に字幕を追加する

generate_subtitle_file() と run() 関数の間に次のコードを追加してください:

def add_subtitle_to_video(soft_subtitle, subtitle_file,  subtitle_language):

    video_input_stream = ffmpeg.input(input_video)
    subtitle_input_stream = ffmpeg.input(subtitle_file)
    output_video = f"output-{input_video_name}.mp4"
    subtitle_track_title = subtitle_file.replace(".srt", "")

    if soft_subtitle:
        stream = ffmpeg.output(
            video_input_stream, subtitle_input_stream, output_video, **{"c": "copy", "c:s": "mov_text"},
            **{"metadata:s:s:0": f"language={subtitle_language}",
            "metadata:s:s:0": f"title={subtitle_track_title}"}
        )
        ffmpeg.run(stream, overwrite_output=True)

ここでは、add_subtitle_to_video()という名前の関数が定義されており、ソフト字幕かハード字幕を追加するかを決定するために使用されるブール値、字幕ファイル名、および書き起こしで検出された言語をパラメーターとして受け取ります。この関数は、入力動画のコピーに対してソフトまたはハード字幕を追加する責任があります。

まず、コードは、入力ビデオと字幕ファイルを使用して ffmpeg.input() メソッドを使い、入力ビデオと字幕ファイルのための入力ストリームオブジェクトを作成し、それらをそれぞれ video_input_stream と subtitle_input_stream という定数に保存します。

入力ストリームを作成した後、コードは出力ビデオファイルの名前を、入力ビデオの基本名に output- を追加し、“.mp4” 拡張子を付けた名前に設定し、この名前を output_video という定数に保存します。さらに、字幕トラックの名前を .srt 拡張子なしの字幕ファイル名に設定し、この名前を subtitle_track_title という定数に保存します。

次に、コードはブール値 soft_subtitle が True に設定されているかどうかを確認し、ソフト字幕を追加する必要があることを示しています。

もしそうであれば、コードはffmpeg.output()メソッドを呼び出して、入力ストリーム、出力ビデオファイル名、および出力ビデオのための次のオプションを持つ出力ストリームオブジェクトを作成します:

  • 「c」:「コピー」とは、ビデオコーデックおよびその他のビデオパラメータが再エンコードせずに、入力から出力に直接コピーされるべきであることを指定します。

  • "c:s": "mov_text"は、字幕のコーデックとパラメータを再エンコードせずに入力から出力にコピーするよう指定します。mov_textは、MP4/MOVファイルで使用される一般的な字幕コーデックです。

  • これは字幕ストリームの言語メタデータを設定します。言語はsubtitle_languageに保存されている値に設定されます。

  • これは字幕ストリームのタイトルメタデータを設定します。タイトルは、subtitle_track_titleに保存されている値に設定されます。

最後に、コードは ffmpeg.run() メソッドを呼び出し、出力ストリームをパラメーターとして渡して動画にソフトサブタイトルを追加し、出力動画ファイルをプロジェクトのルートディレクトリに保存します。

add_subtitle_to_video() 関数の最後に次のコードを追加してください:

def add_subtitle_to_video(soft_subtitle, subtitle_file,  subtitle_language):
    ...
    if soft_subtitle:
        ...
    else:
        stream = ffmpeg.output(video_input_stream, output_video,

                               vf=f"subtitles={subtitle_file}")

        ffmpeg.run(stream, overwrite_output=True)

強調表示されたコードは、ブール値soft_subtitleがFalseに設定されている場合に実行され、ハードサブタイトルを追加することを示しています。

もしそうであれば、まず、コードはffmpeg.output()メソッドを呼び出して、入力ビデオストリーム、出力ビデオファイル名、およびvf=f"subtitles={subtitle_file}"パラメータを持つ出力ストリームオブジェクトを作成します。vfは「ビデオフィルター」を意味し、ビデオストリームにフィルターを適用するために使用されます。この場合、適用されているフィルターは字幕の追加です。

最後に、コードは ffmpeg.run() メソッドを呼び出し、出力ストリームをパラメーターとして渡して動画にハードサブタイトルを追加し、出力動画ファイルをプロジェクトのルートディレクトリに保存します。

run()関数に以下のハイライトされたコードを追加してください:

def run():
    extracted_audio = extract_audio()
    language, segments = transcribe(audio=extracted_audio)
    subtitle_file = generate_subtitle_file(
        language=language,
        segments=segments
    )

    add_subtitle_to_video(
        soft_subtitle=True,
        subtitle_file=subtitle_file,
        subtitle_language=language
    )

強調表示されたコードは、add_subtitle_to_video()を呼び出し、soft_subtitleパラメーターをTrueに設定し、字幕ファイル名と字幕言語を指定して、入力ビデオのコピーにソフト字幕を追加します。

ターミナルに戻り、次のコマンドを実行して main.py スクリプトを実行してください:

  1. python3 main.py

上記のコマンドを実行した後、output-input.mp4という名前の出力動画ファイルがプロジェクトのルートディレクトリに保存されます。

お好みの動画プレーヤーで動画を開き、動画の字幕を選択してください。字幕を選択するまで表示されないことに注意してください。

元のmain.pyファイルに戻り、run()関数に移動し、add_subtitle_to_video()関数の呼び出しでsoft_subtitleパラメータをFalseに設定します:

def run():
    …
    add_subtitle_to_video(
        soft_subtitle=False,
        subtitle_file=subtitle_file,
        subtitle_language=language
    )

ここでは、soft_subtitle パラメータを False に設定して、動画にハード字幕を追加します。

ターミナルに戻り、次のコマンドを実行して main.py スクリプトを実行してください:

  1. python3 main.py

上記のコマンドを実行した後、プロジェクトのルートディレクトリにある output-input.mp4 動画ファイルは上書きされます。

お好みのビデオプレーヤーで動画を開き、動画の字幕を選択しようとしてください。そして、まだ字幕は利用できないのに、字幕が表示されていることに気付いてください。

このセクションでは、SRT字幕ファイルの構造を理解し、前のセクションからの文字起こしセグメントを利用して1つを作成しました。その後、ffmpeg-pythonライブラリを使用して、生成した字幕ファイルをビデオに追加しました。

結論

このチュートリアルでは、ffmpeg-python と faster-whisper の Python ライブラリを使用して、入力動画から音声を抽出し、抽出した音声を文字起こしし、その文字起こしに基づいて字幕ファイルを生成し、その字幕を入力動画のコピーに追加することができるアプリケーションを構築しました。

関連記事: