0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

英語学習のため、自作のExcel英単語・英例文リストから英日音声ファイルを生成してみた

Posted at

1. はじめに

数か月前から英語の勉強を始め、試験を受ける必要も出てきたため、ここしばらく英語学習に取り組んでいます。とはいえ、学生時代から英語だけはどうしても苦手で、ずっと避けてきた科目です。それでももう逃げられなくなり、本格的に勉強を始めたものの、なかなか思うように進みませんでした。

そんな中で出会ったのが「Jump-start」という本です。短い英語のフレーズを聞きながら、自分で話してみたり、シャドーイングしたりして覚える内容で、実際にやってみたところ非常に効率が良いことに気づきました。この方法なら英単語の学習にも応用できるのでは?と思い、試してみたのが今回の記事です。

やったことはシンプルで、まず生成AIを使って英単語を含む短い英文を作成し、それをExcelに一覧化し、さらにその一覧をPythonで音声化し、ウォークマンで聞けるようにした——というのが今回の記事の内容です。

2. 実施環境

CPU: CORE i5 8th Gen
メモリ: 8GB
OS: Windows 11 22H2

3. 事前準備

以下の構成のエクセルファイルを生成AIを使って作成します。

私は最初の1列目だけを自分で作成し、その後は生成AIに指示を出してリストを作成しました。
指示内容としては、「以下の構成で、出力形式はタブ区切り(TSV)とし、Excelに直接貼り付けられる形で出力すること」というものです。

  • 2列目:主要な品詞(例:動詞、名詞、形容詞、副詞など。複数ある場合は主要なものを1つ)
  • 3列目:日本語訳(簡潔な意味)
  • 4列目:その単語を使った自然な英語の例文(現在形・一般文で)
  • 5列目:その例文の自然な日本語訳

作成した英単語リストの一部:

English Part of Speech Japanese Example Sentence Japanese Translation
provide 動詞 提供する The school provides lunch for all students. 学校は全生徒に昼食を提供する。
provide 名詞 提供 The provision of clean water is essential for health. 清潔な水の提供は健康に不可欠である。
merchant 名詞 商人 That merchant sells spices from around the world. その商人は世界中から香辛料を仕入れて販売している。
needle 名詞 She threaded the needle carefully before sewing. 彼女は縫う前に慎重に針に糸を通した。
fasten 動詞 締める Please fasten your seatbelt before takeoff. 離陸前にシートベルトを締めてください。
mental 形容詞 心の He has strong mental resilience to overcome challenges. 彼は困難を乗り越える強い精神的レジリエンスを持っている。

4.モジュールのインストール

既にインストールされている場合は不要ですが、今回のコードは以下のモジュールが必要です。

pip install ffmpeg-python
pip install edge-tts pandas openpyxl pydub

5. 作成したプログラム

今回の記事では以下の特徴を持つプログラムを作成しました。

  • 作成したExcelファイルから、英単語・日本語訳・例文などを読み込み、Edge TTSを使用して音声ファイル(MP3)を自動生成

  • 行番号の順に、1️⃣ 英単語 → 2️⃣ 日本語訳 → 3️⃣ 日本語例文 → 4️⃣ 英語例文の順で音声ファイルを生成

  • 単調な音声にならないよう、20語(行)ごとに男性・女性の話者を交互に切り替え

  • 出力された音声ファイルは、out_edge_tts_split フォルダ内に保存。ファイル名には「行番号+英単語+内容種別(例:_en_word, _ja_example)」が含まれる

make_vocab_edge_tts.py
import asyncio
import re
from pathlib import Path
import math
import pandas as pd
import edge_tts

IN_XLSX = "入力のエクセル.xlsx"
SHEET   = 0
OUT_DIR = Path("out_edge_tts_split")

# 列インデックス(0始まり)
COL_EN_WORD    = 0  # A: 英単語
COL_JA_MEAN    = 2  # C: 日本語訳
COL_JA_EX_SENT = 4  # E: 例文の日本語訳
COL_EN_EX_SENT = 3  # D: 例文の英語

BLOCK_SIZE = 20  # 20行ごとに男女切替

# 英語ボイス(男性/女性)
EN_MALE, EN_FEMALE = "en-US-GuyNeural", "en-US-JennyNeural"
# 日本語ボイス(男性/女性)
JA_MALE, JA_FEMALE = "ja-JP-KeitaNeural", "ja-JP-NanamiNeural"

def slugify(s: str, maxlen: int = 40) -> str:
    s = re.sub(r"\s+", "_", str(s).strip())
    s = re.sub(r"[^A-Za-z0-9_\-]", "", s)
    return (s[:maxlen] or "item").lower()

def pick_voice_pair(block_index: int, is_japanese: bool):
    """20行ごとに男女切替。英/日で別々に判定。"""
    if is_japanese:
        return (JA_MALE if block_index % 2 == 0 else JA_FEMALE)
    else:
        return (EN_MALE if block_index % 2 == 0 else EN_FEMALE)

def norm(x) -> str:
    if x is None:
        return ""
    if isinstance(x, float) and math.isnan(x):
        return ""
    return str(x).strip()

async def tts_save(text: str, voice: str, path: Path):
    if not text:
        return False
    path.parent.mkdir(parents=True, exist_ok=True)
    comm = edge_tts.Communicate(text=text, voice=voice)
    with open(path, "wb") as f:
        async for chunk in comm.stream():
            if chunk["type"] == "audio":
                f.write(chunk["data"])
    return True

async def main():
    OUT_DIR.mkdir(parents=True, exist_ok=True)

    # ヘッダ有無を簡易判定
    df_probe = pd.read_excel(IN_XLSX, sheet_name=SHEET, header=None)
    first = df_probe.iloc[0].astype(str).str.lower().tolist()
    has_header = any(k in " ".join(first) for k in ["ja", "en", "japanese", "english", "英語", "日本語", "word", "meaning", "単語"])

    if has_header:
        df = pd.read_excel(IN_XLSX, sheet_name=SHEET)  # ヘッダあり
        rows = list(df.itertuples(index=False))
        get  = lambda row, idx: row[idx]
        base_idx = 2  
    else:
        df = df_probe
        rows = list(df.itertuples(index=False))
        get  = lambda row, idx: row[idx]
        base_idx = 1

    en_playlist = []
    ja_playlist = []

    for i, row in enumerate(rows, start=base_idx):
        en_word    = norm(get(row, COL_EN_WORD)    if len(row) > COL_EN_WORD    else "")
        ja_mean    = norm(get(row, COL_JA_MEAN)    if len(row) > COL_JA_MEAN    else "")
        ja_ex_sent = norm(get(row, COL_JA_EX_SENT) if len(row) > COL_JA_EX_SENT else "")
        en_ex_sent = norm(get(row, COL_EN_EX_SENT) if len(row) > COL_EN_EX_SENT else "")

        if not any([en_word, ja_mean, ja_ex_sent, en_ex_sent]):
            print(f"[SKIP] 行{i}: 空行")
            continue

        # 20行ごと性別切替(英・日で別々)
        en_block = (i - base_idx) // BLOCK_SIZE
        ja_block = en_block  

        en_voice = pick_voice_pair(en_block, is_japanese=False)
        ja_voice = pick_voice_pair(ja_block, is_japanese=True)

        # ファイル名ベース
        base_slug = slugify(en_word) or f"row{i}"

        # --- 英(A→D)を別々に保存 ---
        en_files_this_row = []
        if en_word:
            p = OUT_DIR / f"{i:04d}_{base_slug}_1_en_word.mp3"
            ok = await tts_save(en_word, en_voice, p)
            if ok:
                en_files_this_row.append(p.name)

        if en_ex_sent:
            p = OUT_DIR / f"{i:04d}_{base_slug}_4_en_example.mp3"
            ok = await tts_save(en_ex_sent, en_voice, p)
            if ok:
                en_files_this_row.append(p.name)

        # --- 日(C→E)を別々に保存 ---
        ja_files_this_row = []
        if ja_mean:
            p = OUT_DIR / f"{i:04d}_{base_slug}_2_mean.mp3"
            ok = await tts_save(ja_mean, ja_voice, p)
            if ok:
                ja_files_this_row.append(p.name)

        if ja_ex_sent:
            p = OUT_DIR / f"{i:04d}_{base_slug}_3_ja_example.mp3"
            ok = await tts_save(ja_ex_sent, ja_voice, p)
            if ok:
                ja_files_this_row.append(p.name)

        # プレイリスト(行ごと順番を担保)
        en_playlist.extend(en_files_this_row)
        ja_playlist.extend(ja_files_this_row)

        print(f"[OK] 行{i}  EN:{en_voice}  JA:{ja_voice}  "
              f"(en_files={len(en_files_this_row)}, ja_files={len(ja_files_this_row)})")

    # プレイリスト出力
    if en_playlist:
        with open(OUT_DIR / "playlist_en.m3u", "w", encoding="utf-8") as f:
            f.write("#EXTM3U\n")
            for name in en_playlist:
                f.write(name + "\n")
    if ja_playlist:
        with open(OUT_DIR / "playlist_ja.m3u", "w", encoding="utf-8") as f:
            f.write("#EXTM3U\n")
            for name in ja_playlist:
                f.write(name + "\n")

    print("完成:", OUT_DIR)

if __name__ == "__main__":
    asyncio.run(main())

6. 実行

python make_vocab_edge_tts.py

実行後、out_edge_tts_split/ フォルダに以下のようなファイルが生成されます。

0001_hello_1_en_word.mp3
0001_hello_2_mean.mp3
0001_hello_3_ja_example.mp3
0001_hello_4_en_example.mp3
playlist_en.m3u
playlist_ja.m3u
  • 1〜20行目:男性声
  • 21〜40行目:女性声
  • 41〜60行目:男性声 …のように交互に切り替わります。

7. 結果やまとめ

このプログラムを使うことで、英単語帳のExcelファイルから音声付きの語彙学習素材を作成し、ウォークマンなどのデバイスで再生できるようになりました。発音やイントネーションに多少の違和感がある部分もありますが、個人の英語学習用としては十分に実用的だと思いました(あくまで私の感想です)。

また、英語音声を聞きながら同じ内容を文字で確認すると、学習効果が高まるとも言われています。そのため、次のステップとしては、今回作成した英単語・例文のExcelファイルから、電子書籍として読めるPDF教材を作成していきたいと考えています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?