コピペ作業をゼロに!Python×AnkiConnectで作る「最強語学ツール」の開発術

プログラミング

✔ Pythonの基礎は学んだものの、アイデアが思い浮かばない…
✔ APIや外部ツールを連携させるシステムは難しそう…
✔ 謎のエラーが連発して挫折しかけている…
✔ 単語帳作成を自動化する、コードとアーキテクチャを知りたい!


YouTube動画と拡張機能「Language Reactor」を活用した語学学習は非常に効果的ですが、復習用アプリ「Anki」へメディア(画像・音声)付きのカードを登録する作業が大きなボトルネックになります。この「絶望的な手作業」を解決するため、Pythonを活用してAnkiカードを全自動で生成するシステムを構築しました。

本記事では、Pythonのデータ処理ライブラリや動画処理ツール(yt-dlp、FFmpeg)、そしてAnki連携API(AnkiConnect)を組み合わせたシステムの全体アーキテクチャを解説します。

単なる完成コードの紹介だけでなく、開発過程で実際にぶつかった「3つの壁(エラー)」とその解決策も詳しく紹介します。プログラミングで日常の課題を解決する「実践的なITリテラシー」を身につけるためのステップアップとして、ぜひ参考にしてください。

この記事でわかること
  • Excel形式の出力データから、自動で抽出するデータ処理の手法
  • YouTube動画から指定した「画像」と「音声」を切り出す技術
  • AnkiConnectを経由して、Ankiへ直接データを流し込む連携方法
  • 開発現場で起こりがちなトラブルを論理的に解決する思考法

システムの全体アーキテクチャと必要な環境

まずは、作成したシステムの全体像を把握しましょう。結論から言うと、5つの技術をリレーのように繋いで自動化を実現しています。

処理の流れ(5つのステップ)

手作業で行っていた工程を、プログラムでは以下のように置き換えました。

ステップ使用する技術・ツール処理の内容(役割)
1. データ準備Language Reactor保存した学習フレーズやタイムスタンプをExcel形式でエクスポートする。
2. 読み込みPython (pandas)エクスポートしたExcelの表データを、Pythonで扱える形に読み込む。
3. 動画URL取得yt-dlp対象となるYouTube動画のストリーミング用URL(裏側のデータURL)を取得する。
4. メディア抽出FFmpeg取得したURLから、指定した「秒数」の画像と音声を高速で切り出す。
5. Ankiへ登録AnkiConnect (API)切り出した画像・音声とテキストデータを、Ankiアプリに直接送信(POST)する。

必要なライブラリと外部ツール

本システムを動かすためには、Python本体に加えて以下のツールが必要です。

  • pandas / openpyxl:表計算データ(ExcelやCSV)を効率よく読み書きするためのPythonライブラリです。
  • yt-dlp:YouTubeなどの動画サイトから、動画や音声のデータを取得するための強力なライブラリです。
  • FFmpeg:動画や音声のフォーマット変換、切り出しを行うための世界標準のマルチメディアツールです(※PCへのインストールとパス通しが必要です)。
  • AnkiConnect:外部のプログラムからAnkiを操作できるようにする、Anki用の無料アドオンです。

実装でぶつかった「3つの壁」とエラー解決策

一見シンプルなデータ連携に見えますが、実際に開発を進めるといくつかの「壁」にぶつかりました。ここでは、その原因と論理的な解決策を解説します。

壁1:字幕のタイムスタンプと映像のズレ

【問題】

抽出したスクショ画像が、話し手の表情がまだ作られていなかったり、前のシーンの残像だったりして、不自然なカードになってしまう問題が発生しました。

【解決策】

これは、字幕が表示されるタイミングと、映像のベストショットのタイミングが完全に一致していないことが原因です。

そこで、Pythonのコード内で動的にタイミングを補正する変数を追加しました。

  • TIME_OFFSET = 1(字幕のタイムスタンプから1秒後の画像を抽出する)
  • AUDIO_OFFSET = 0(音声は字幕通りのタイミングで取得する)

このように、画像と音声で取得タイミングをずらす工夫をすることで、常に自然な表情のスクショを安定して生成できるようになりました。

壁2:AnkiConnectの「cannot create note」エラー

【問題】

AnkiConnectを経由してデータを送信した際、ターミナルに cannot create note because it is empty(データが空なのでノートを作成できません)というエラーが表示されました。

【解決策】

Pythonからは確実にデータを送っているのになぜ「空」と言われるのでしょうか?原因は「フィールド名(データの入れ物の名前)」の不一致でした。

Ankiのデフォルトのフィールド名は「Front(表面)」「Back(裏面)」です。しかし、私のAnki環境では「センテンス」「画像」「音声」という独自のフィールド名にカスタマイズしていました。

Python側からデータを送る際(JSONのPayload作成時)、この自分専用のフィールド名に正確にマッピング(紐付け)して送信するようにコードを修正することで、無事に登録されるようになりました。

壁3:pandas読み込み時の「KeyError: ‘Time’」

【問題】

Excelデータを pandas で読み込もうとした直後、KeyError: 'Time' というエラーが出て処理が止まってしまいました。

【解決策】

「KeyError」とは、指定した列の名前(キー)が見つからないというエラーです。

原因を調べると、Language Reactorのエクスポート仕様の罠がありました。「字幕全体をエクスポートした場合」と「保存したアイテムのみをエクスポートした場合」で、出力されるExcelのカラム名(列のタイトル)が微妙に変わっていたのです。

Excelの実際のデータ構造を直接目で確認し、Pythonコード内で読み込む列の名前を実際のファイルに合わせて修正することで解決しました。エラーが出た時は、「元データが本当に自分が想定している形か?」を疑うことが重要です。

最終的なPythonコードと実行方法

上記の課題をすべてクリアした最終的なスクリプトがこちらです。ご自身の環境に合わせて、ファイルパスやフィールド名を変更して活用してください。

スクリプト
import pandas as pd
import yt_dlp
import subprocess
import requests
import base64
import os

# === 1. 設定エリア ===
VIDEO_URL = "https://www.youtube.com/watch?v=?????" # 対象のYouTubeのURL
EXCEL_FILE = "??????????.xlsx" # エクセルファイル名
DECK_NAME = "?????" # 保存先のAnkiデッキ名

# 画像のタイミング設定
TIME_OFFSET = 1 # 画像の取得を何秒ずらすか(+1秒など)

# ★音声のタイミング設定
AUDIO_OFFSET = 0 # 音声の開始を何秒ずらすか(少し前から録音したい場合は -1 など)
AUDIO_DURATION = 3 # 音声を何秒間切り出すか(例: 4秒間)

# === 2. 時間の変換関数 ===
def parse_time(time_str):
    time_str = str(time_str).replace('s', '').strip()
    if ':' in time_str:
        parts = time_str.split(':')
        if len(parts) == 2:
            return int(parts[0]) * 60 + int(parts[1])
        elif len(parts) == 3:
            return int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2])
    return int(float(time_str))

# === 3. メイン処理 ===
print("エクセルを読み込んでいます...")
df = pd.read_excel(EXCEL_FILE)

print("動画の裏側データ(映像+音声)を取得中...")
# 音声も含まれる形式('best')に変更
ydl_opts = {'format': 'best'}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    info = ydl.extract_info(VIDEO_URL, download=False)
    stream_url = info['url']

# エクセルの行数分だけループ
for index, row in df.iterrows():
    time_raw = row['Time']
    subtitle = row['Subtitle']
    translation = row['Human Translation']

    # 時間計算
    base_seconds = parse_time(time_raw)
    capture_seconds = max(0, base_seconds + TIME_OFFSET)
    audio_start = max(0, base_seconds + AUDIO_OFFSET)

    img_filename = f"youtube_snap_{base_seconds}.jpg"
    audio_filename = f"youtube_audio_{base_seconds}.mp3"

    print(f"\n[{time_raw}] のカードを作成中... (画像: {capture_seconds}秒, 音声: {audio_start}秒から{AUDIO_DURATION}秒間)")

    # ① FFmpegで【画像】を切り出し
    cmd_img = [
        'ffmpeg', '-ss', str(capture_seconds), '-i', stream_url,
        '-vframes', '1', '-q:v', '2', img_filename, '-y'
    ]
    subprocess.run(cmd_img, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    # ② FFmpegで【音声】を切り出し (mp3化)
    cmd_aud = [
        'ffmpeg', '-ss', str(audio_start), '-i', stream_url,
        '-t', str(AUDIO_DURATION), '-vn', '-acodec', 'libmp3lame', '-q:a', '2', audio_filename, '-y'
    ]
    subprocess.run(cmd_aud, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    # ③ 画像をAnkiに送信
    with open(img_filename, "rb") as f:
        img_b64 = base64.b64encode(f.read()).decode('utf-8')
    requests.post('http://localhost:8765', json={
        "action": "storeMediaFile", "version": 6,
        "params": {"filename": img_filename, "data": img_b64}
    })

    # ④ 音声をAnkiに送信
    with open(audio_filename, "rb") as f:
        aud_b64 = base64.b64encode(f.read()).decode('utf-8')
    requests.post('http://localhost:8765', json={
        "action": "storeMediaFile", "version": 6,
        "params": {"filename": audio_filename, "data": aud_b64}
    })

    # ⑤ Ankiにカードを追加(音声フィールドを追加!)
    note_payload = {
        "action": "addNote", "version": 6,
        "params": {
            "note": {
                "deckName": DECK_NAME,
                "modelName": "Basic",
                "fields": {
                    "センテンス": f"{subtitle}", #自分のAnkiのフィールド名に変更
                    "日本語の意味": f"{translation}", #自分のAnkiのフィールド名に変更
                    "画像": f"<img src='{img_filename}'>",
                    "音声": f"[sound:{audio_filename}]"
                },
                "options": {"allowDuplicate": False},
                "tags": ["YouTube"]
            }
        }
    }
    response = requests.post('http://localhost:8765', json=note_payload)
    res_json = response.json()

    if res_json.get('error') is None:
        print("✅ Ankiに追加完了!")
    else:
        print(f"❌ エラー: {res_json.get('error')}")

    # 使い終わった一時ファイルを削除
    os.remove(img_filename)
    os.remove(audio_filename)

print("\n🎉 すべてのカード作成が完了しました!Ankiを確認してください。")

まとめ:日常の課題はプログラミングで解決できる!

今回紹介した技術(yt-dlpFFmpeg、API通信)は、それぞれ単体でも強力ですが、組み合わせることで「1日に数十分かかっていた面倒な手作業を、完全にゼロにする」という絶大な価値を生み出しました。

プログラミングやITリテラシーの本来の目的は、難しいコードを書くことではなく、「自分の日常にある課題を解決すること」です。

現在の仕様ではLanguage Reactorからのエクスポート作業が手動ですが、次は「Selenium(ブラウザ自動化ツール)」を使って、このExcel出力すらも完全自動化することに挑戦してみたいと思います。

皆さんもぜひ、身の回りの「面倒くさい」をヒントに、自分だけの自動化ツール開発に挑戦してみてください!

💡 開発の経緯や、コードを書かずにAIを使ってツールを作る方法を知りたい方はこちらの記事へ!

語学学習の単語帳作りを自動化!IT初心者が作った「自動化ツール」開発体験記
YouTubeでの語学学習を効率化したい方必見!プログラミング未経験の文系でも、AIを活用して「Ankiカード自動作成ツール」を作る方法を解説します。面倒なスクショや音声の切り抜き作業から解放され、ITリテラシーも高まる実践的なノウハウです。

コメント

タイトルとURLをコピーしました