LangChainを利用して、英語の字幕を英語学習の教材に変換する方法

## 英語学習をサポートする自動字幕処理ツールの作成
英語の学習は、時には退屈に感じることがありますよね。そこで、私は好きなアニメや映画を使って学習する方法を試してみることにしました。具体的には、これらの映像から字幕を抽出し、それをGPTを活用して学習素材に変換するのです。LangChainで動画ファイルを扱い、有効な学習ツールを作成していきます。

#### **処理の流れ**:
  1. 指定された動画ファイルから字幕を抽出。
  2. 抽出した字幕ファイルを読み込み、適切なサイズに分割。
  3. 各部分に対して整形と解説を実行。
  4. 解説結果をMarkdownファイルに追記。

#### 1. 動画から字幕を抽出する

まず、`ffprobe`と`ffmpeg`といったツールを用いて、動画ファイルから字幕ストリームを抽出します。以下の関数`extract_subtitles`では、指定した動画ファイルから字幕を抽出し、SRT形式で保存します。

```python
def extract_subtitles(video_path):
    global object_name
    object_name = os.path.basename(video_path).split('.')[0]
    cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_streams', video_path]
    result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    streams = json.loads(result.stdout)
    subtitle_stream_index = None
    for stream in streams['streams']:
        if stream['codec_type'] == 'subtitle':
            subtitle_stream_index = stream['index']
            break

    if subtitle_stream_index is None:
        print("字幕ストリームが見つかりませんでした。")
    cmd = ['ffmpeg', '-y', '-i', video_path, '-map', f'0:{subtitle_stream_index}', '-c:s', 'srt', f"./sub{object_name}.srt"]
    subprocess.run(cmd, check=True)
    print(f"字幕は {object_name}.srt に保存されます。")
```
この関数は、動画ファイルのパスを受け取り、字幕ストリームを検出してSRTファイルとして保存します。字幕が存在しない場合は警告メッセージを表示します。

#### 2.入力token数が制限されてますので、字幕を分割してGPTに入力するのは無難です。

`split_subtitles`関数を用いて字幕を適切なサイズに分割します。ここでは、1つの部分に最大15個の字幕セクションを含めるように設定しています。

```python
def split_subtitles(subtitle_file: str, max_number_per_part: int = 15):
    lines = subtitle_file.strip().split('\n')
    parts = []
    current_part = []
    current_index = 0
    
    for line in lines:
        if line.strip().isdigit():
            if current_index >= max_number_per_part:
                parts.append('\n'.join(current_part))
                current_part = []
                current_index = 0
            current_index += 1
        current_part.append(line)   
    # 最後の部分を追加
    if current_part:
        parts.append('\n'.join(current_part))
    return parts
```

この関数により、大量の字幕を小さなチャンクに分割し、後続の処理を効率化します。

#### 3. 言語モデルを用いた字幕の整形と解説

次に、LangChainとOpenAIの言語モデルを活用して、抽出した字幕を整形し、さらにその文法や意味を解説します。

まず、`ChatOpenAI`クラスを使用して言語モデルを設定します。

```python
llm = ChatOpenAI(
    model='deepseek-chat',
    openai_api_key='sk-...',
    openai_api_base='https://api.deepseek.com',
    max_tokens=1024*4, # deepseek-chat APIは4096トークンに対応
    temperature=0
)
```

#### 4. 字幕を文章に変換するチェーン

`sub_to_sentence_chain`では、抽出した字幕を適切な英文に組み立てます。プロンプトは日本語で記述されており、具体的な例を通じてモデルに指示を与えています。

```python
sub_to_sentence_chain = LLMChain(
    llm=llm,
    prompt=ChatPromptTemplate.from_template(
    """\
私は現在英語を勉強しています。あなたは英語の専門家として、以下のことを行います。これは英語字幕です。
今、以下のことをする必要があります。
1. 各字幕を抽出し、それらを適切な完全な文に組み立て、改行文字を追加してください。文以外の出力はしないでください。
例えば：
10
00:01:44,880 --> 00:01:46,000
<font face="Noto Sans" size="55">were you two able to get done</font>

11
00:01:46,080 --> 00:01:47,750
<font face="Noto Sans" size="55">with all your
homework and stuff?</font>

出力:
were you two able to get done with all your homework and stuff?\n

現在のテキストは以下の通りです：
{subtitle}

"""
    ),
    output_key="sentences"
)
```

#### 5. 文法解説を行うチェーン

`explain_chain`では、整形された英文を基に、文法構造や意味を中国語で詳細に解説します。

```python
explain_chain = LLMChain(
    llm=llm,
    prompt=ChatPromptTemplate.from_template(
    """\
あなたは英語の教師で、私は英語が四級程度しかできない中国の学生です。対話はできるだけ中国語で行い、私の英語レベルに合わせて、私が入力した内容に基づいて英語の指導を行ってください。小説の内容を紹介する場合でも、私が直接入力する場合でも、1文ずつ中国語で説明し、文法構造（文の成分と品詞）やその他の難点を付け加えてください。名詞や動詞の変形が不規則な場合は原形を提示し、不規則変形であることを指摘してください。他の語彙については原形を使用して説明してください。以下の例に厳密に従い、1文ずつ説明してください。説明以外の出力はしないでください。

例:
It might be hard to play games then. But it is a good idea to have something to distract us..

- It might be hard to play games then.
  * it /ɪt/ pron. 它
  * might /maɪt/ aux. 可能,也许
  * be /biː/ v. 是
  * hard /hɑːrd/ adj. 困難な
  * to play games 遊ぶこと（不定詞が補語）
  * then /ðen/ adv. その時、そして
解釈: その時、ゲームをするのは少し難しいかもしれません。
文法: これは単純文です。主語は It、述語は might be、hard は補語、to play games は不定詞が補語、then は時の副詞句です。

- But it is a good idea to have something to distract us.
  * a good idea 良い考え
  * to have 持つこと（不定詞が補語）
  * something /'sʌmθɪŋ/ pron. 何か
  * to distract 注意をそらすこと（不定詞が修飾語）
  * us /ʌs/ pron. 私たち
解釈: しかし、私たちの注意をそらすものを持つことは良い考えです。
文法: これは単純文です。主語は It、述語は is、a good idea は補語、to have something to distract us は不定詞が補語です。

テキストは以下の通りです：
{sentences}
"""),
    output_key="explanations"
)
```

このプロンプトにより、学習者は英文の各文について詳細な文法解説を得ることができます。

#### 6. チェーンのシーケンス実行

`SequentialChain`を用いて、字幕の整形から文法解説までを順次実行します。

```python
sequence_chain = SequentialChain(
    chains=[sub_to_sentence_chain, explain_chain],
    input_variables=['subtitle'],
    output_variables=['sentences', 'explanations'],
    verbose=True
)
```

#### 7. 処理の実行と結果の保存

`main`関数では、上記で定義した各機能を組み合わせて、動画から字幕を抽出し、分割・整形・解説を行い、最終的な解説をMarkdownファイルとして保存します。

```python
def main():
    extract_subtitles(os.path.abspath(f"D:/MyData/study/New folder/01(1)/english_study_agent/video/06.mkv"))
    with open(f"./sub{object_name}.srt", encoding='utf-8') as f:
        subtitle = f.read()
    parts = split_subtitles(subtitle)
    runnable = RunnableLambda(
        lambda x: sequence_chain.invoke(x),
    )
    for index, part in enumerate(parts):
        print(f"part: {index+1}, 処理開始、全体 {len(parts)} 部分")
        result = runnable.with_retry(
            stop_after_attempt=10
        ).invoke(part)
        print(result['sentences'])
        print(result['explanations'])
        print(f"part: {index+1}, 処理終了")
        with open(f"{object_name}.md", "a", encoding='utf-8') as f:
            f.write(result['explanations'] + '\n\n')

if __name__ == "__main__":
    main()
```

github:https://github.com/cagayakeKON/tools/blob/main/analysis_jp.py

結果は以下のように

- Okay, let's do this.
  * okay /oʊ'keɪ/ interj. わかった
  * let's /lɛts/ pron. 私たちは
  * do /duː/ v. する
  * this /ðɪs/ pron. これ
  日本語訳: わかった、これをやりましょう。
  文法: 主語は 'let's'（let us の短縮形）、述語は 'do'、目的語は 'this' です。'okay' は感嘆詞で、同意や確認を表しています。

- Sure, let's go.
  * sure /ʃʊr/ adv. 確かに
  * let's /lɛts/ pron. 私たちは
  * go /goʊ/ v. 行く
  日本語訳: 確かに、行きましょう。
  文法: 主語は 'let's'（let us の短縮形）、述語は 'go' です。'sure' は副詞で、同意や確認を強調しています。

- Grr!
  * grr /grr/ interj. うなり声
  日本語訳: うなり声！
  文法: 'grr' は感嘆詞で、感情や反応を表しています。

- Quit screwing around and start!
  * quit /kwɪt/ v. やめる
  * screwing around 遊び回る（動名詞）
  * and /ænd/ conj. そして
  * start /stɑːrt/ v. 始める
  日本語訳: 遊び回るのをやめて、始めなさい！
  文法: 主語は省略されていますが、暗黙の主語があります。述語は 'quit' と 'start' で、'screwing around' は動名詞で 'quit' の目的語、'and' は並列接続詞です。