こんにちは。ソニー・ミュージックエンタテインメントでWebエンジニアをしている万次郎です。
みなさんは、音声の文字起こしをどのように行なっていますか?手作業での書き起こしは時間がかかりますし、自動文字起こしツールを使っても精度に悩まされることがあるかもしれません。
最近では、OpenAI の Whisper や、その改良版である WhisperX などが登場し、高精度な文字起こし技術が話題になっています。しかし、実は特定の条件下では Whisper や WhisperX よりも高精度な方法 があります。それが Montreal Forced Aligner(MFA) です。
この記事ではMFAのしくみから、実際にどのように使うのかまでを一通り解説します。記事の最後には生成された字幕と実際の音声を同期させた作例を掲載していますので、ぜひその精度の高さをご確認いただければと思います。
- MFAとは?文字起こしとは何が違うのか
- MFAの仕組み
- MFAの使い方
- 最後に
MFAとは?文字起こしとは何が違うのか
Whisperなどの自動文字起こしツールは、「音声を聞いて一からテキストを生成」しますが、MFAは「すでにあるテキストを音声と一致させる」ことに特化しています。そのため、スクリプトが事前にある場合には、Whisper よりも高精度に単語ごとのタイムスタンプを取得できる のが特徴です。つまり、WhisperやWhisperXはイニシャルプロンプトの設定は可能なものの、基本的にはゼロから文字起こしできるのに対して、MFAはテキストデータとそれを読み上げた音声データのペアを用いて音声の整列(アラインメント)を行います。
名称 | 方式 | 入力データ |
---|---|---|
Whisper | 文字起こし | 音声データ |
WhisperX | 文字起こし | 音声データ |
MFA | 音声アライメント | 音声データ and テキストデータ |
音声データを扱う際に直面する大きな課題の一つが、音声とテキストの同期です。例えば、「この音声の中で、どの時点でどの単語が発声されているのか」を特定する作業は、音声合成や音声認識の研究開発において重要な基盤となります。この同期作業を自動化するツールの一つが、Montreal Forced Aligner(MFA)なのです。
MFAの仕組み
MFAは、音声ファイルとテキストを入力として、各単語や音素(言語を構成する最小の音の単位)が音声のどの時点で発声されているかを自動的に特定します。具体的には、録音された音声に「こんにちは」というテキストが対応している場合、「こ」「ん」「に」「ち」「は」それぞれの開始時刻と終了時刻を推定するわけです。
この作業を正確に行うために、MFAはいくつかの段階的なアプローチを採用しています。
ここでは音声アライメント(音声の整列)を行うために重要なプロセスを大まかに4つに分けて解説します。
1. 音素の初期位置の特定
第1段階は「モノフォンモデル」と呼ばれる初期アラインメントです。「こんにちは」という単語を例にとると、まず音声を細かい時間枠(フレーム)に分割し、各フレームがどの音素(「こ」「ん」「に」「ち」「は」)に対応するかを推定します。この段階では、各音素を完全に独立したものとして扱い、音声の特徴(音の高さ、大きさ、周波数特性など)から最も可能性の高い音素を割り当てていきます。言い換えるなら、音声という連続した流れの中で、音素をざっくばらんに整列させていくようなものです。
2. 精緻化
第2段階では「トライフォンモデル」という、より洗練された手法を使います。人間が話すとき、ある音は前後の音の影響を受けて微妙に変化します。例えば「こんにちは」の「ん」は、前の「こ」と後ろの「に」の影響で、単独の「ん」とは少し異なる音になります。この段階では、そうした音の変化のパターンをモデル化し、より正確な位置の特定を行います。これにより、自然な発話の流れをより正確に捉えることができるのです。
3. 特徴量変換による識別性能の向上
第3段階では、音声特徴量に対して「LDA(Linear Discriminant Analysis:線形判別分析)」と「MLLT(Maximum Likelihood Linear Transform:最尤線形変換)」という2つの技術的な処理を行います。これらは音声データを、音素の違いがより明確になるように変換する技術です。例えると、写真の明るさやコントラストを調整して細部をより見やすくする作業に似ています。この処理により、似たような音(例えば「か」と「が」)の区別がより正確になり、アラインメントの精度が向上します。
4. 話者適応による最終調整
最後の第4段階は「話者適応」です。音声には話者固有の特徴があります。例えば、同じ「こんにちは」でも、話者によって声の高さ、スピード、抑揚のつけ方が異なります。この段階では、その話者特有の音声パターンを学習し、モデルを調整します。技術的には「fMLLR(feature-space Maximum Likelihood Linear Regression)」という手法を使用し、話者ごとに音声特徴量を補正します。これにより、個人の話し方の癖を考慮した、より正確なアラインメントが可能になります。
これらの段階的な手順を踏むことで、高精度な音声とテキストの同期を可能にしているのです。
MFAの使い方
MFAのインストールにはconda-forge
を利用します。conda-forge
はオープンソースコミュニティによって提供されているので、MFA
自体は無料で利用することができます。
以降ではmac上のMiniconda
環境でインストールする方法とDocker
環境でインストールする方法の2通り紹介します。
Minicondaとconda-forgeを使用するインストール方法
前提: Miniconda※がインストール済みであること
※Anacondaでの利用も可能ですが、Anacondaは従業員 200 名以上の企業での商用利用には、Business または Enterprise ライセンスが必要ですし、MFAはMinicondaでも十分動作しますので、今回はMinicondaを利用します。
ステップ1: 作業用ディレクトリの準備
お好きなディレクトリ内でmfaというディレクトリを作成し、作成したディレクトリ内に移動してください。
mkdir mfa cd mfa
最終的なディレクトリ/ファイルの構成はこちらのようになる想定です。
mfa/ ├── input/ │ └── test/ │ ├── test.txt <= 入力ファイル │ └── test.wav <= 入力ファイル └── output/ └── test.TextGrid <= 出力ファイル(実行後に生成されるファイルです)
ステップ2: 仮想環境の作成とアクティベート
プロジェクトごとに依存関係を分離するため、仮想環境を作成することをおすすめします。ここではPython 3.10
を利用した環境を作成します。
conda create -n mfa_env python=3.10
作成した仮想環境を有効化するには、次のコマンドを実行します。
conda activate mfa_env
この環境内でMFAおよび関連パッケージが動作するため、他のプロジェクトとの依存関係の衝突を避けることができます。
ステップ3: Condaチャネルの設定
まず、Miniconda環境で以下のコマンドを実行してconda-forgeチャネルを追加します。
conda config --add channels conda-forge
ステップ4: MFAのインストール
conda-forgeチャネルが追加された状態で、MFAをインストールします。
conda install montreal-forced-aligner
ステップ5: 必要なモジュールのインストール
ステップ3でインストールはされないものの、MFAを動作させるためには必要なモジュールをインストールします。
conda install -c conda-forge spacy sudachipy sudachidict-core
ステップ6: MFAの動作確認
インストールが完了したら、以下のコマンドでMFAのバージョンやヘルプ情報を確認できます。
mfa --help
出力
Usage: mfa [OPTIONS] COMMAND [ARGS]... Montreal Forced Aligner is a command line utility for aligning speech and text. ╭─ Options ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ --help Show this message and exit. │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ adapt Adapt an acoustic model │ │ align Align a corpus │ │ align_one Align a single file │ │ anchor Launch Anchor │ │ configure The configure command is used to set global defaults for MFA so you don't have to set them every time you call an MFA command. │ │ diarize Diarize a corpus │ │ g2p Generate pronunciations │ │ history Show previously run mfa commands │ │ model Download, inspect, and save models │ │ models Download, inspect, and save models │ │ segment Split long audio files into shorter segments │ │ segment_vad Split long audio files into shorter segments │ │ server Start, stop, and delete MFA database servers │ │ tokenize Tokenize utterances │ │ train Train a new acoustic model │ │ train_dictionary Calculate pronunciation probabilities │ │ train_g2p Train a G2P model │ │ train_ivector Train an ivector extractor │ │ train_lm Train a language model │ │ train_tokenizer Train a tokenizer model │ │ transcribe Transcribe audio files │ │ transcribe_speechbrain Transcribe utterances using an ASR model trained by SpeechBrain │ │ transcribe_whisper Transcribe utterances using a Whisper ASR model via faster-whisper │ │ validate Validate corpus │ │ validate_dictionary Validate dictionary │ │ version Show version of MFA │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
以上で準備は完了です。
続いて実際に動かすための準備とコマンドをご紹介します。
ステップ7: 日本語辞書と日本語音響モデルのダウンロード
推論を行うために、日本語辞書と日本語の音響モデルをローカルにダウンロードします。
日本語辞書のダウンロード
mfa model download dictionary japanese_mfa
日本語音響モデルのダウンロード
mfa model download acoustic japanese_mfa
実行後、下記のようなパスにファイルが保存されます。
/Users/ユーザー名/Documents/MFA/pretrained_models/dictionary/japanese_mfa.dict /Users/ユーザー名/Documents/MFA/pretrained_models/acoustic/japanese_mfa.zip
実行環境によってダウンロード先が異なることにご注意ください。
ステップ8: アライメント対象のデータの準備
日本語音響モデルを利用したアライメントを行うためには、以下の2種類のファイルが必要です。
- 音声ファイル(.wav)
- 対応する文字起こしや台本ファイル(.txt)
これらのファイルは、同じフォルダに配置し、かつファイル名(拡張子を除く)を同一にしてください。
※厳密には同一である必要はありませんが、実行のわかりやすさのため今回は同一にしています。
例えば、以下のように配置してください。
mfa/ └── input/ └── test/ ├── test.txt └── test.wav
私はテストとして、4秒程度沈黙した後に「私の名前は万次郎です。最近はモンスターハンターにハマっています。」と発話したwavファイル
と「私の名前は万次郎です。最近はモンスターハンターにハマっています。」と記載したtxtファイル
を用意しました。
ステップ9: MFAによるアライメントの実行
準備が整ったら、ターミナルで次のコマンドを実行します。
この例では、入力フォルダ、音響モデル、および出力フォルダをそれぞれ指定しています。
mfa align ./input/test/ japanese_mfa ~/Documents/MFA/pretrained_models/acoustic/japanese_mfa.zip ./output/test/
./input/test/
: 音声ファイルと文字起こしが入ったフォルダのパスjapanese_mfa
: 使用する音響モデルの名前(ダウンロードしたモデルの識別子)~/Documents/MFA/pretrained_models/acoustic/japanese_mfa.zip
: ダウンロード済みの音響モデルのZIPファイルのパス./output/test
: アラインメント結果を出力するフォルダのパス(任意の場所でOK)
私の場合は、入力ファイルがあるフォルダ直下にoutput/test
というフォルダを作成し、そこに結果を出力しています。
ステップ10: 結果の確認
出力先フォルダ(今回の場合はoutput/test
)には、入力と同名のTextGrid
ファイルが生成され、各音素や単語の開始・終了時刻が記録されています。
item [1]
には基本的には単語ベースの出力、 item [2]
には音素ベースで出力されます。
test.TextGrid
の出力```
File type = "ooTextFile"
Object class = "TextGrid"
xmin = 0
xmax = 9.636281
tiers? <exists>
size = 2
item []:
item [1]:
class = "IntervalTier"
name = "words"
xmin = 0
xmax = 9.636281
intervals: size = 18
intervals [1]:
xmin = 0.0
xmax = 2.37
text = ""
intervals [2]:
xmin = 2.37
xmax = 2.81
text = "私"
intervals [3]:
xmin = 2.81
xmax = 2.93
text = "の"
intervals [4]:
xmin = 2.93
xmax = 3.26
text = "名前"
intervals [5]:
xmin = 3.26
xmax = 3.52
text = "は"
intervals [6]:
xmin = 3.52
xmax = 3.62
text = ""
intervals [7]:
xmin = 3.62
xmax = 4.2
text = "万次郎"
intervals [8]:
xmin = 4.2
xmax = 4.62
text = "です"
intervals [9]:
xmin = 4.62
xmax = 5.04
text = ""
intervals [10]:
xmin = 5.04
xmax = 5.59
text = "最近"
intervals [11]:
xmin = 5.59
xmax = 5.82
text = "は"
intervals [12]:
xmin = 5.82
xmax = 5.96
text = ""
intervals [13]:
xmin = 5.96
xmax = 6.48
text = "モンスター"
intervals [14]:
xmin = 6.48
xmax = 6.9
text = "ハンター"
intervals [15]:
xmin = 6.9
xmax = 7.02
text = "に"
intervals [16]:
xmin = 7.02
xmax = 7.48
text = "ハマって"
intervals [17]:
xmin = 7.48
xmax = 8.01
text = "います"
intervals [18]:
xmin = 8.01
xmax = 9.636281
text = ""
item [2]:
class = "IntervalTier"
name = "phones"
xmin = 0
xmax = 9.636281
intervals: size = 63
intervals [1]:
xmin = 0.0
xmax = 2.37
text = ""
intervals [2]:
xmin = 2.37
xmax = 2.48
text = "w"
intervals [3]:
xmin = 2.48
xmax = 2.53
text = "a"
intervals [4]:
xmin = 2.53
xmax = 2.6
text = "t"
intervals [5]:
xmin = 2.6
xmax = 2.65
text = "a"
intervals [6]:
xmin = 2.65
xmax = 2.77
text = "ɕ"
intervals [7]:
xmin = 2.77
xmax = 2.81
text = "i"
intervals [8]:
xmin = 2.81
xmax = 2.85
text = "n"
intervals [9]:
xmin = 2.85
xmax = 2.93
text = "o"
intervals [10]:
xmin = 2.93
xmax = 2.99
text = "n"
intervals [11]:
xmin = 2.99
xmax = 3.04
text = "a"
intervals [12]:
xmin = 3.04
xmax = 3.12
text = "m"
intervals [13]:
xmin = 3.12
xmax = 3.2
text = "a"
intervals [14]:
xmin = 3.2
xmax = 3.26
text = "e"
intervals [15]:
xmin = 3.26
xmax = 3.34
text = "w"
intervals [16]:
xmin = 3.34
xmax = 3.52
text = "a"
intervals [17]:
xmin = 3.52
xmax = 3.62
text = ""
intervals [18]:
xmin = 3.62
xmax = 3.72
text = "m"
intervals [19]:
xmin = 3.72
xmax = 3.8
text = "a"
intervals [20]:
xmin = 3.8
xmax = 3.88
text = "ɲ"
intervals [21]:
xmin = 3.88
xmax = 3.97
text = "dʑ"
intervals [22]:
xmin = 3.97
xmax = 4.03
text = "i"
intervals [23]:
xmin = 4.03
xmax = 4.09
text = "ɾ"
intervals [24]:
xmin = 4.09
xmax = 4.2
text = "oː"
intervals [25]:
xmin = 4.2
xmax = 4.26
text = "d"
intervals [26]:
xmin = 4.26
xmax = 4.35
text = "e"
intervals [27]:
xmin = 4.35
xmax = 4.56
text = "s"
intervals [28]:
xmin = 4.56
xmax = 4.62
text = "ɨ̥"
intervals [29]:
xmin = 4.62
xmax = 5.04
text = ""
intervals [30]:
xmin = 5.04
xmax = 5.19
text = "s"
intervals [31]:
xmin = 5.19
xmax = 5.25
text = "a"
intervals [32]:
xmin = 5.25
xmax = 5.35
text = "i"
intervals [33]:
xmin = 5.35
xmax = 5.44
text = "c"
intervals [34]:
xmin = 5.44
xmax = 5.53
text = "i"
intervals [35]:
xmin = 5.53
xmax = 5.59
text = "ɴ"
intervals [36]:
xmin = 5.59
xmax = 5.67
text = "w"
intervals [37]:
xmin = 5.67
xmax = 5.82
text = "a"
intervals [38]:
xmin = 5.82
xmax = 5.96
text = ""
intervals [39]:
xmin = 5.96
xmax = 6.02
text = "m"
intervals [40]:
xmin = 6.02
xmax = 6.1
text = "o"
intervals [41]:
xmin = 6.1
xmax = 6.16
text = "ɰ̃"
intervals [42]:
xmin = 6.16
xmax = 6.23
text = "s"
intervals [43]:
xmin = 6.23
xmax = 6.31
text = "t"
intervals [44]:
xmin = 6.31
xmax = 6.48
text = "aː"
intervals [45]:
xmin = 6.48
xmax = 6.54
text = "h"
intervals [46]:
xmin = 6.54
xmax = 6.62
text = "a"
intervals [47]:
xmin = 6.62
xmax = 6.7
text = "n"
intervals [48]:
xmin = 6.7
xmax = 6.79
text = "t"
intervals [49]:
xmin = 6.79
xmax = 6.9
text = "aː"
intervals [50]:
xmin = 6.9
xmax = 6.96
text = "ɲ"
intervals [51]:
xmin = 6.96
xmax = 7.02
text = "i"
intervals [52]:
xmin = 7.02
xmax = 7.08
text = "h"
intervals [53]:
xmin = 7.08
xmax = 7.12
text = "a"
intervals [54]:
xmin = 7.12
xmax = 7.21
text = "m"
intervals [55]:
xmin = 7.21
xmax = 7.27
text = "a"
intervals [56]:
xmin = 7.27
xmax = 7.43
text = "tː"
intervals [57]:
xmin = 7.43
xmax = 7.48
text = "e"
intervals [58]:
xmin = 7.48
xmax = 7.54
text = "i"
intervals [59]:
xmin = 7.54
xmax = 7.62
text = "m"
intervals [60]:
xmin = 7.62
xmax = 7.7
text = "a"
intervals [61]:
xmin = 7.7
xmax = 7.93
text = "s"
intervals [62]:
xmin = 7.93
xmax = 8.01
text = "ɨ̥"
intervals [63]:
xmin = 8.01
xmax = 9.636281
text = ""
```
次にMFAのDocker環境での構築方法を紹介します。
Docker環境でのMFAの利用方法
この手順を実行するには、以下のツールがインストールされている必要があります:
- Docker (動作確認済みバージョン: 27.3.1)
- docker compose (動作確認済みバージョン: v2.30.3)
ステップ1: プロジェクト構成
まずは以下のようなディレクトリ構造でプロジェクトを構成します:
. ├──mfa/ │ ├── docker/ │ │ └── Dockerfile │ └── environment.yml ├── common/ │ └── test/ │ ├── test.wav <= 入力ファイル │ ├── test.txt <= 入力ファイル │ └── test.TextGrid <= 出力(実行後に生成されることに注意してください) └── docker-compose.yaml
ディレクトリ/ファイル構成を参考に、Dockerfile
, environment.yml
, docker-compose.yaml
を配置してください。
それぞれのファイルの中身は下記のとおりです。
mfa/docker/Dockerfile
の中身
FROM condaforge/miniforge3:latest # 作業ディレクトリを作成 WORKDIR /opt/mfa # environment.yml をコピーして conda 環境を作成 COPY environment.yml /tmp/environment.yml RUN conda env create -f /tmp/environment.yml -n mfa_env && \ conda clean -afy # requirements.txt をコピーして pip インストール(必要に応じて) COPY requirements.txt /tmp/requirements.txt RUN conda run -n mfa_env pip install -r /tmp/requirements.txt # Montreal Forced Aligner (MFA) をインストール RUN conda run -n mfa_env conda install -c conda-forge montreal-forced-aligner -y # コンテナ起動時に mfa_env が自動で有効化されるように設定 RUN echo "conda activate mfa_env" >> /root/.bashrc # MFA がパスで使えるように設定 ENV PATH /opt/conda/envs/mfa_env/bin:$PATH # dictionary (辞書) と acoustic (アコースティックモデル) をダウンロード RUN mfa model download dictionary japanese_mfa RUN mfa model download acoustic japanese_mfa # 最終的な作業ディレクトリ WORKDIR /app # 対話的にコンテナを使うために bash を起動 CMD ["/bin/bash"]
このDockerfile
では以下の処理を行っています:
- Condaベースイメージからスタート
- MFA実行環境の構築
- 日本語用の辞書と音響モデルをダウンロード
- 対話的に使えるようにbashをエントリーポイントに設定
mfa/environment.yml
には、MFAの実行に必要な依存パッケージを記述します:
channels: - conda-forge - pytorch - nvidia - anaconda dependencies: - python>=3.8 - numpy - librosa - pysoundfile - tqdm - requests - pyyaml - dataclassy - kaldi=*=*cpu* - scipy - pynini - openfst=1.8.3 - scikit-learn - hdbscan - baumwelch - ngram - praatio=6.0.0 - biopython=1.79 - sqlalchemy>=2.0 - pgvector - pgvector-python - sqlite - postgresql - psycopg2 - click - setuptools_scm - pytest - pytest-mypy - pytest-cov - pytest-timeout - mock - coverage - coveralls - interrogate - kneed - matplotlib - seaborn - pip - rich - rich-click - kalpy # Tokenization dependencies - spacy - sudachipy - sudachidict-core - spacy-pkuseg - pip: - build - twine # Tokenization dependencies - python-mecab-ko - jamo - pythainlp - hanziconv - dragonmapper
この設定ファイルは公式GitHubリポジトリから取得したものですが、一部不要な依存関係が含まれている可能性があります。プロジェクトの要件に応じて最適化することもできます。
docker-compose.yaml
の中身
version: "3.9" services: mfa: build: context: ./mfa dockerfile: docker/Dockerfile stdin_open: true tty: true volumes: - ./mfa:/app - ./common:/app/common - ./mfa/cache:/root/.cache restart: "no"
このファイルではボリュームマウントを設定し、ホスト側のファイルシステムとコンテナ内のファイルシステムを連携させています。
これで必要なファイルの準備は完了です。
ステップ2: アライメント対象のデータの準備
日本語音響モデルを利用したアライメントを行うためには、以下の2種類のファイルが必要です。
- 音声ファイル(.wav)
- 対応する文字起こしや台本ファイル(.txt)
これらのファイルは、同じフォルダに配置し、かつファイル名(拡張子を除く)が同一である必要があります。
例えば、以下のように配置してください。
└── common/ └── test/ ├── test.wav └── test.txt
私はテストとして、4秒程度沈黙した後に「私の名前は万次郎です。最近はモンスターハンターにハマっています。」と発話したwavファイル
と「私の名前は万次郎です。最近はモンスターハンターにハマっています。」と記載したtxtファイル
を用意しました。
ステップ3: コンテナのビルド & ラン
それではターミナルからコンテナをビルドして実行しましょう。
まずコンテナをビルドしてください。
docker compose build mfa
ビルドが完了したら、コンテナを立ち上げてその中に入ります。
docker compose run --rm mfa
コンテナに接続すると、以下のようなプロンプトが表示されます:
(mfa_env) root@hogehoge:/app#
このプロンプトは、すでにMFA用のconda環境(mfa_env
)がアクティブになっていることを示しています。
続いてMFAを実行しましょう。
ステップ4: MFAの実行
コンテナ内で以下のコマンドを実行して、音声とテキストのアライメントを行います:
mfa align ./common/test/ japanese_mfa /root/Documents/MFA/pretrained_models/acoustic/japanese_mfa.zip ./common/test/
このコマンドの構造は以下の通りです:
mfa align
- アライメント実行コマンド./common/test/
- 入力ファイル(音声とテキスト)のディレクトリjapanese_mfa
- 使用する辞書/root/Documents/MFA/pretrained_models/acoustic/japanese_mfa.zip
- 音響モデルへのパス./common/test/
- 出力先ディレクトリ
実行が完了すると、指定した出力先ディレクトリにtest.TextGrid
ファイルが生成されます。このファイルには、テキストの各単語や音素が音声データのどの時間位置に対応しているかという情報が含まれています。
ステップ5: 実行内容の確認
では、結果を確認していきましょう。今回の場合はcommon/test/
に出力されています。
common/test
内のtest.TextGridの出力はこのとおりです。
common/test.TextGrid
の出力File type = "ooTextFile"
Object class = "TextGrid"
xmin = 0
xmax = 9.636281
tiers? <exists>
size = 2
item []:
item [1]:
class = "IntervalTier"
name = "words"
xmin = 0
xmax = 9.636281
intervals: size = 18
intervals [1]:
xmin = 0.0
xmax = 2.37
text = ""
intervals [2]:
xmin = 2.37
xmax = 2.81
text = "私"
intervals [3]:
xmin = 2.81
xmax = 2.93
text = "の"
intervals [4]:
xmin = 2.93
xmax = 3.26
text = "名前"
intervals [5]:
xmin = 3.26
xmax = 3.52
text = "は"
intervals [6]:
xmin = 3.52
xmax = 3.62
text = ""
intervals [7]:
xmin = 3.62
xmax = 4.2
text = "万次郎"
intervals [8]:
xmin = 4.2
xmax = 4.62
text = "です"
intervals [9]:
xmin = 4.62
xmax = 5.04
text = ""
intervals [10]:
xmin = 5.04
xmax = 5.59
text = "最近"
intervals [11]:
xmin = 5.59
xmax = 5.82
text = "は"
intervals [12]:
xmin = 5.82
xmax = 5.96
text = ""
intervals [13]:
xmin = 5.96
xmax = 6.48
text = "モンスター"
intervals [14]:
xmin = 6.48
xmax = 6.9
text = "ハンター"
intervals [15]:
xmin = 6.9
xmax = 7.02
text = "に"
intervals [16]:
xmin = 7.02
xmax = 7.48
text = "ハマって"
intervals [17]:
xmin = 7.48
xmax = 8.01
text = "います"
intervals [18]:
xmin = 8.01
xmax = 9.636281
text = ""
item [2]:
class = "IntervalTier"
name = "phones"
xmin = 0
xmax = 9.636281
intervals: size = 63
intervals [1]:
xmin = 0.0
xmax = 2.37
text = ""
intervals [2]:
xmin = 2.37
xmax = 2.48
text = "w"
intervals [3]:
xmin = 2.48
xmax = 2.53
text = "a"
intervals [4]:
xmin = 2.53
xmax = 2.6
text = "t"
intervals [5]:
xmin = 2.6
xmax = 2.65
text = "a"
intervals [6]:
xmin = 2.65
xmax = 2.77
text = "ɕ"
intervals [7]:
xmin = 2.77
xmax = 2.81
text = "i"
intervals [8]:
xmin = 2.81
xmax = 2.85
text = "n"
intervals [9]:
xmin = 2.85
xmax = 2.93
text = "o"
intervals [10]:
xmin = 2.93
xmax = 2.99
text = "n"
intervals [11]:
xmin = 2.99
xmax = 3.04
text = "a"
intervals [12]:
xmin = 3.04
xmax = 3.12
text = "m"
intervals [13]:
xmin = 3.12
xmax = 3.2
text = "a"
intervals [14]:
xmin = 3.2
xmax = 3.26
text = "e"
intervals [15]:
xmin = 3.26
xmax = 3.34
text = "w"
intervals [16]:
xmin = 3.34
xmax = 3.52
text = "a"
intervals [17]:
xmin = 3.52
xmax = 3.62
text = ""
intervals [18]:
xmin = 3.62
xmax = 3.72
text = "m"
intervals [19]:
xmin = 3.72
xmax = 3.8
text = "a"
intervals [20]:
xmin = 3.8
xmax = 3.88
text = "ɲ"
intervals [21]:
xmin = 3.88
xmax = 3.97
text = "dʑ"
intervals [22]:
xmin = 3.97
xmax = 4.03
text = "i"
intervals [23]:
xmin = 4.03
xmax = 4.09
text = "ɾ"
intervals [24]:
xmin = 4.09
xmax = 4.2
text = "oː"
intervals [25]:
xmin = 4.2
xmax = 4.26
text = "d"
intervals [26]:
xmin = 4.26
xmax = 4.35
text = "e"
intervals [27]:
xmin = 4.35
xmax = 4.56
text = "s"
intervals [28]:
xmin = 4.56
xmax = 4.62
text = "ɨ̥"
intervals [29]:
xmin = 4.62
xmax = 5.04
text = ""
intervals [30]:
xmin = 5.04
xmax = 5.19
text = "s"
intervals [31]:
xmin = 5.19
xmax = 5.25
text = "a"
intervals [32]:
xmin = 5.25
xmax = 5.35
text = "i"
intervals [33]:
xmin = 5.35
xmax = 5.44
text = "c"
intervals [34]:
xmin = 5.44
xmax = 5.53
text = "i"
intervals [35]:
xmin = 5.53
xmax = 5.59
text = "ɴ"
intervals [36]:
xmin = 5.59
xmax = 5.67
text = "w"
intervals [37]:
xmin = 5.67
xmax = 5.82
text = "a"
intervals [38]:
xmin = 5.82
xmax = 5.96
text = ""
intervals [39]:
xmin = 5.96
xmax = 6.02
text = "m"
intervals [40]:
xmin = 6.02
xmax = 6.1
text = "o"
intervals [41]:
xmin = 6.1
xmax = 6.16
text = "ɰ̃"
intervals [42]:
xmin = 6.16
xmax = 6.23
text = "s"
intervals [43]:
xmin = 6.23
xmax = 6.31
text = "t"
intervals [44]:
xmin = 6.31
xmax = 6.48
text = "aː"
intervals [45]:
xmin = 6.48
xmax = 6.54
text = "h"
intervals [46]:
xmin = 6.54
xmax = 6.62
text = "a"
intervals [47]:
xmin = 6.62
xmax = 6.7
text = "n"
intervals [48]:
xmin = 6.7
xmax = 6.79
text = "t"
intervals [49]:
xmin = 6.79
xmax = 6.9
text = "aː"
intervals [50]:
xmin = 6.9
xmax = 6.96
text = "ɲ"
intervals [51]:
xmin = 6.96
xmax = 7.02
text = "i"
intervals [52]:
xmin = 7.02
xmax = 7.08
text = "h"
intervals [53]:
xmin = 7.08
xmax = 7.12
text = "a"
intervals [54]:
xmin = 7.12
xmax = 7.21
text = "m"
intervals [55]:
xmin = 7.21
xmax = 7.27
text = "a"
intervals [56]:
xmin = 7.27
xmax = 7.43
text = "tː"
intervals [57]:
xmin = 7.43
xmax = 7.48
text = "e"
intervals [58]:
xmin = 7.48
xmax = 7.54
text = "i"
intervals [59]:
xmin = 7.54
xmax = 7.62
text = "m"
intervals [60]:
xmin = 7.62
xmax = 7.7
text = "a"
intervals [61]:
xmin = 7.7
xmax = 7.93
text = "s"
intervals [62]:
xmin = 7.93
xmax = 8.01
text = "ɨ̥"
intervals [63]:
xmin = 8.01
xmax = 9.636281
text = ""
出力結果を動画に合成
TextGridファイルは複数の層(item)で構成され、通常、単語情報はitem[1]に格納されています。この情報を抽出してSRT形式に変換することで、ほとんどの動画プレイヤーやエディタで使用できる字幕ファイルが生成できます。
具体的には、以下のプロセスで変換を行います:
- TextGridファイルからitem[1]の情報(開始時間、終了時間、テキスト)を抽出
- 時間情報をSRT互換形式(HH:MM:SS,mmm)に変換
- 連番と書式を適用してSRTファイルを生成
- 生成したSRTファイルを動画に適用
1-3までのプロセスを行ってくれるスクリプトもご紹介します。
test.TextGrid
をパースするためのpythonスクリプト
def format_timestamp(seconds): hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) seconds = seconds % 60 milliseconds = int((seconds % 1) * 1000) seconds = int(seconds) return f"{hours:02d}:{minutes:02d}:{seconds:02d},{milliseconds:03d}" def parse_textgrid(file_path): intervals = [] with open(file_path, 'r', encoding='utf-8') as f: lines = f.readlines() item1_start = -1 for i, line in enumerate(lines): if line.strip() == "item [1]:": item1_start = i break if item1_start == -1: raise ValueError("Could not find item [1] in TextGrid file") intervals_start = -1 for i in range(item1_start, len(lines)): if lines[i].strip().startswith("intervals ["): intervals_start = i break if intervals_start == -1: raise ValueError("Could not find intervals in TextGrid file") current_interval = {} for line in lines[intervals_start:]: line = line.strip() # Check if we've reached item [2] if line == "item [2]:": break if line.startswith("intervals ["): if current_interval and 'xmin' in current_interval and 'xmax' in current_interval and 'text' in current_interval: intervals.append(current_interval) current_interval = {} elif line.startswith("xmin = "): current_interval['xmin'] = float(line.split("=")[1].strip()) elif line.startswith("xmax = "): current_interval['xmax'] = float(line.split("=")[1].strip()) elif line.startswith("text = "): text = line.split("=")[1].strip() # Remove quotes if present if text.startswith('"') and text.endswith('"'): text = text[1:-1] current_interval['text'] = text if current_interval and 'xmin' in current_interval and 'xmax' in current_interval and 'text' in current_interval: intervals.append(current_interval) return intervals def create_srt(intervals): srt_entries = [] counter = 1 for interval in intervals: # Skip empty text entries if not interval['text']: continue start_time = format_timestamp(interval['xmin']) end_time = format_timestamp(interval['xmax']) srt_entry = f"{counter}\n{start_time} --> {end_time}\n{interval['text']}\n" srt_entries.append(srt_entry) counter += 1 return "\n".join(srt_entries) def textgrid_to_srt(input_file, output_file): try: intervals = parse_textgrid(input_file) srt_content = create_srt(intervals) with open(output_file, 'w', encoding='utf-8') as f: f.write(srt_content) print(f"Successfully converted {input_file} to {output_file}") except Exception as e: print(f"Error converting file: {str(e)}") if __name__ == "__main__": input_file = "test.TextGrid" output_file = "test.srt" textgrid_to_srt(input_file, output_file)
出力されたSRT(test.srt
)ファイル
1 00:00:02,370 --> 00:00:02,810 私 2 00:00:02,810 --> 00:00:02,930 の 3 00:00:02,930 --> 00:00:03,259 名前 4 00:00:03,259 --> 00:00:03,520 は 5 00:00:03,620 --> 00:00:04,200 万次郎 6 00:00:04,200 --> 00:00:04,620 です 7 00:00:05,040 --> 00:00:05,589 最近 8 00:00:05,589 --> 00:00:05,820 は 9 00:00:05,960 --> 00:00:06,480 モンスター 10 00:00:06,480 --> 00:00:06,900 ハンター 11 00:00:06,900 --> 00:00:07,019 に 12 00:00:07,019 --> 00:00:07,480 ハマって 13 00:00:07,480 --> 00:00:08,009 います
生成されたSRTファイルを実際に活用するフェーズに入ります。字幕の流し込みにはさまざまなツールが利用可能です。代表的なものとしてffmpegを使用する方法や、Adobe Premiere Proのような業界標準の動画編集ソフトウェアを利用する方法があります。 今回の検証では、処理の一貫性を保つためにffmpegを選択し、アライメントのために用意したwavファイルの音声を合成した動画に対し、字幕の流し込みをコマンドラインで処理しました。以下が実行したコマンドの例です:
ffmpeg -i 元動画.mp4 -vf subtitles=生成したSRTファイル.srt -c:a copy test_with_cc_with_audio.mp4
処理後の動画であるtest_with_cc_with_audio.mp4
を確認したところ、音声と字幕のタイミングが非常に高精度で同期していることが確認できました。特に単語レベルでの同期精度は、従来の自動字幕生成ツールと比較しても遜色ない結果となりました。
このように、MFAを活用することで、高品質な字幕付き動画を効率的に作成することが可能です。以上、Montreal Forced Aligner (MFA) の概要と実践的な使い方についての解説でした。
最後に
MFA(Montreal Forced Aligner)は特定の条件下において、OpenAIのWhisperやその改良版であるWhisperXと比較しても、より高精度にテキストと音声の同期を実現できることが分かりました。
しかし、このような便利なツールにも限界があります。例えば、台本上にエクスクラメーションマーク(!)やクエスチョンマーク(?)が含まれている場合でも、MFAは音声のアライメントのみに焦点を当てるため、出力されるTextGridファイルからこれらの記号は除外されてしまいます。また、TextGrid形式のファイルをそのままパースしてSRTファイルに変換した場合、単語の区切りのみで字幕が追加されることになり、読みやすさや自然な区切りが損なわれる可能性があります。
こうした課題に対して、近年注目を集めているLLM(大規模言語モデル)を活用することで解決策が見えてきます。LLMを組み合わせることで、MFAの技術的限界をある程度克服できる可能性があります。次回以降の記事では、「LLMを活用してMFAの弱点をどのように克服するか」というテーマについて詳しく掘り下げていく予定です。
長文になりましたが、MFAについての解説は以上となります。最後までお読みいただき、誠にありがとうございました。
次回の記事もどうぞお楽しみに!