自動で暗記用フラッシュカード動画を作成する学習用プログラムの解説

コーディング


暗記用フラッシュカード動画を自動生成する学習プログラムの解説

この記事では、TSVファイルからテキストを読み込み、それに対応する画像と音声を自動で組み合わせてMP4形式のフラッシュカード動画を生成するPythonプログラムについて解説します。


プログラムの概要

このプログラムは、以下の主要な処理を自動でおこないます。

      1. TSVファイルの読み込み: あらかじめ用意したTSVファイル(タブ区切りテキスト)から、日本語と英語の単語やフレーズを読み込みます。

      1. 画像の自動生成:
            • 日本語: 読み込んだ日本語テキストを画像に変換します。

            • 英語: 読み込んだ英語テキストを検索クエリとしてGoogle画像検索をおこない、そのスクリーンショットを取得します。

        1. 音声の自動生成と結合:
              • 日本語と英語: それぞれのテキストを音声に変換します。

              • 音声の結合: 日本語音声、無音、英語音声、無音の順に音声を結合します。

          1. 動画の自動生成: 生成した画像と結合した音声をFFmpegコマンドで組み合わせて、1つのMP4動画を作成します。

        このプログラムは、語学学習用の教材や単語帳を効率よく作成したい場合にとても役立ちます。


        プログラムの実行に必要な準備

        プログラムを実行する前に、以下のライブラリと外部ツールのインストールが必要です。

        1. Pythonライブラリのインストール

        コマンドプロンプトやターミナルを開き、以下のコマンドを一行ずつ実行して必要なライブラリをインストールします。

        Bash

        pip install gtts
        pip install pydub
        pip install Pillow # PILはPillowというライブラリ名でインストールします
        pip install selenium
        pip install webdriver-manager
        

        2. FFmpegのインストール

        pydubsubprocessで音声・動画を処理するために、FFmpegという外部ツールが必要です。Pythonライブラリではないため、別途インストールする必要があります。

            • Windowsの場合:
                  • FFmpeg公式サイトからファイルをダウンロードし、解凍します。

                  • 解凍したフォルダ内のbinフォルダにあるffmpeg.exeのパスを環境変数Pathに追加してください。

                  • パスの追加方法が不明な場合は、「Windows 環境変数 Path 追加」で検索すると詳しい情報が見つかります。

              • macOSの場合:
                • Homebrewを使っている場合は、以下のコマンドで簡単にインストールできます。

                Bashbrew install ffmpeg

              • Linuxの場合:
                • 各ディストリビューションのパッケージマネージャーを使用します。

                Bash# Debian/Ubuntu系 sudo apt update sudo apt install ffmpeg # Fedora系 sudo dnf install ffmpeg

            3. Chromeブラウザのインストール

            Seleniumとwebdriver-managerはChromeブラウザを操作するために使用されるため、PCにGoogle Chromeがインストールされている必要があります。まだインストールしていない場合は、Google Chromeの公式サイトからダウンロードしてインストールしてください。


            プログラムの解説

            提供されたコードは、大きく分けて以下の6つのセクションで構成されています。

            1. 初期設定と準備

                • ライブラリのインポート: プログラムの実行に必要なライブラリを読み込みます。

                • パラメータ設定: 生成されたファイル(画像、音声、動画)を保存する出力ディレクトリOUTPUT_DIRや、読み込むTSVファイルのパスTSV_FILE、言語設定lang1lang2、無音時間silence_durationなどを設定します。

                • Chromeドライバー設定関数 (set_driver): Selenium WebDriverを使ってChromeブラウザを操作するための設定をおこないます。--headlessオプションにより、ブラウザは画面に表示されずにバックグラウンドで動作します。

              2. テキスト画像生成関連関数

                  • wrap_text関数: 長いテキストが画像からはみ出さないように、指定された幅で自動的に改行します。

                  • create_text_image関数: 日本語テキストを読み込み、白背景に黒文字で中央に配置したPNG画像を作成します。wrap_text関数を使ってテキストを適切に折り返します。

                3. スクリーンショット保存関数

                    • save_screenshot関数: 英語テキストを検索クエリとして使用し、Google画像検索のスクリーンショットをPNG画像として保存します。特定のサイト(irasutoya.com)に限定して検索をおこなう設定も含まれています。

                  4. 音声生成関連関数

                      • generate_audio関数: Google Text-to-Speech (gTTS) を利用して、指定されたテキストを音声ファイル(MP3形式)に変換し保存します。一時的なエラーが発生した場合は、リトライする処理も実装されています。

                      • get_audio_duration関数: pydubを使って、音声ファイルの再生時間を秒単位で取得します。

                    5. メイン処理ループ

                        • TSVファイルの読み込み: プログラムは指定されたTSVファイルを読み込み、各行を日本語と英語のテキストとして扱います。

                        • 画像・音声の生成と結合: 読み込んだテキストから、日本語のテキスト画像と英語のスクリーンショット、そしてそれぞれの音声を生成します。その後、音声ファイルを結合し、WAV形式で保存します。

                        • 動画の生成: ffmpegコマンドを呼び出し、生成された画像と結合した音声ファイルを組み合わせて、最終的なMP4動画ファイルを出力します。この際、画像の表示時間や動画のフレームレートなどが音声の長さに合わせて自動で調整されます。

                      6. 終了処理

                          • driver.quit(): すべての処理が完了したら、プログラムはSelenium WebDriverを終了し、バックグラウンドで動作していたChromeブラウザのプロセスを閉じます。


                        このプログラムを使用することで、TSVファイルに単語リストを用意するだけで、すぐに学習に使えるフラッシュカード動画を効率的に作成できます。

                        import os

                        import subprocess

                        from gtts import gTTS

                        from pydub import AudioSegment

                        from PIL import Image, ImageDraw, ImageFont

                        from io import BytesIO

                        import time

                        from selenium import webdriver

                        from webdriver_manager.chrome import ChromeDriverManager

                        from selenium.webdriver.chrome.service import Service

                        # パラメータ設定

                        OUTPUT_DIR = r”C:\Users\kosei\Downloads\denki”

                        TSV_FILE = r”C:\Users\kosei\Downloads\問題 – シート1 (1).tsv”

                        #TSV_FILE = input(“TSVファイルのパスを入力してください: “)

                        os.makedirs(OUTPUT_DIR, exist_ok=True)

                        # 言語設定

                        lang1 = ‘ja’ # 1番目の言語 (日本語)

                        lang2 = ‘ja’ # 2番目の言語 (英語)

                        # 空白の秒数指定

                        silence_duration = 2000 # 2秒の無音

                        # Chromeドライバーをセットアップする関数

                        def set_driver():

                        options = webdriver.ChromeOptions()

                        options.add_argument(‘–headless’)

                        return webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)

                        # 文字サイズを大きくするための設定

                        FONT_SIZE = 30 # 文字サイズを大きくする

                        ”’

                        # テキストを画像として保存する関数

                        def create_text_image(text, index, font_size=FONT_SIZE):

                        img_path = os.path.join(OUTPUT_DIR, f”image_{index:03d}.png”)

                        image = Image.new(‘RGB’, (640, 480), color=(255, 255, 255)) # 白背景

                        draw = ImageDraw.Draw(image)

                        font = ImageFont.truetype(r”C:\Windows\Fonts\msyh.ttc”, font_size) # フォントは色々な外国語で文字化けしないmsyhを使用

                        text_bbox = draw.textbbox((0, 0), text, font=font)

                        text_width = text_bbox[2] – text_bbox[0]

                        text_height = text_bbox[3] – text_bbox[1]

                        position = ((640 – text_width) // 2, (480 – text_height) // 2)

                        draw.text(position, text, fill=(0, 0, 0), font=font) # 黒文字でテキストを描画

                        image.save(img_path)

                        return img_path

                        ”’

                        # フォントサイズ

                        FONT_SIZE = 20

                        # テキストを折り返す関数

                        def wrap_text(text, font, max_width):

                        words = text.split()

                        lines = []

                        current_line = “”

                        for word in words:

                        test_line = current_line + ” ” + word if current_line else word

                        text_width = font.getbbox(test_line)[2] # getbboxでテキストの幅を取得

                        if text_width > max_width: # 指定幅を超えたら改行

                        lines.append(current_line)

                        current_line = word

                        else:

                        current_line = test_line

                        if current_line:

                        lines.append(current_line)

                        return lines

                        # テキストを画像として保存する関数

                        def create_text_image(text, index, font_size=FONT_SIZE):

                        img_path = os.path.join(OUTPUT_DIR, f”image_{index:03d}.png”)

                        image = Image.new(‘RGB’, (640, 480), color=(255, 255, 255)) # 白背景

                        draw = ImageDraw.Draw(image)

                        font = ImageFont.truetype(r”C:\Windows\Fonts\msyh.ttc”, font_size)

                        max_width = 600 # 画像の横幅に対する最大テキスト幅

                        wrapped_text = wrap_text(text, font, max_width) # テキストを折り返し処理

                        # 各行の高さを取得

                        line_height = font.getbbox(“A”)[3] – font.getbbox(“A”)[1]

                        total_text_height = line_height * len(wrapped_text)

                        # テキストの描画開始位置(中央揃え)

                        y_offset = (480 – total_text_height) // 2

                        for line in wrapped_text:

                        text_width = font.getbbox(line)[2]

                        x_position = (640 – text_width) // 2

                        draw.text((x_position, y_offset), line, fill=(0, 0, 0), font=font)

                        y_offset += line_height

                        image.save(img_path)

                        return img_path

                        ”’

                        # 日本語の単語をGoogle画像検索し、スクリーンショットを保存する関数

                        def save_screenshot(driver, word, index):

                        img_path = os.path.join(OUTPUT_DIR, f”image_{index:03d}.png”)

                        driver.get(f”https://www.google.com/search?hl=ja&tbm=isch&q={word}のイメージ -形容詞 -動詞 -名刺 -副詞 -前置詞 -接続詞”)

                        time.sleep(0.1)

                        screenshot = driver.get_screenshot_as_png()

                        image = Image.open(BytesIO(screenshot))

                        image.save(img_path)

                        return img_path

                        ”’

                        # 日本語の単語をGoogle画像検索し、スクリーンショットを保存する関数

                        def save_screenshot(driver, word, index):

                        img_path = os.path.join(OUTPUT_DIR, f”image_{index:03d}.png”)

                        # 不要な文字を削除(-形容詞 など)

                        remove_words = [“形容詞:”, “:動詞:”, “名詞:”, “副詞:”, “前置詞:”, “接続詞:”]

                        clean_word = word

                        for remove_word in remove_words:

                        clean_word = clean_word.replace(remove_word, “”)

                        # Google画像検索を実行

                        driver.get(f”https://www.google.com/search?hl=ja&tbm=isch&q={clean_word} https://www.irasutoya.com”)

                        time.sleep(0.1)

                        # スクリーンショットを撮影

                        screenshot = driver.get_screenshot_as_png()

                        image = Image.open(BytesIO(screenshot))

                        image.save(img_path)

                        return img_path

                        # テキストを音声に変換して保存する関数

                        def generate_audio(text, lang, filename, retries=3):

                        path = os.path.join(OUTPUT_DIR, filename)

                        for _ in range(retries):

                        try:

                        tts = gTTS(text=text, lang=lang)

                        tts.save(path)

                        return path

                        except Exception as e:

                        print(f”エラーが発生しました: {e}. リトライします…”)

                        time.sleep(3)

                        raise Exception(“音声生成に失敗しました。”)

                        # 音声の長さを取得する関数

                        def get_audio_duration(audio_path):

                        audio = AudioSegment.from_file(audio_path)

                        return audio.duration_seconds

                        # TSVファイルを読み込む

                        with open(TSV_FILE, ‘r’, encoding=’utf-8′) as file:

                        tsv_data = [line.strip().split(‘\t’) for line in file]

                        # ドライバーのセットアップ

                        driver = set_driver()

                        # 各行について処理を実行

                        for index, row in enumerate(tsv_data):

                        ja_text = row[0] # 最初の列の日本語テキスト

                        en_text = row[1] # 2番目の列の英語テキスト

                        # 日本語のテキスト画像を作成

                        ja_text_image_path = create_text_image(ja_text, index * 2)

                        # 英語のスクリーンショットを保存

                        en_screenshot_path = save_screenshot(driver, en_text, index * 2 + 1)

                        # 日本語の音声を生成

                        japanese_audio_path = generate_audio(ja_text, lang1, f”audio_ja_{index}.mp3″)

                        # 英語の音声を生成

                        english_audio_path = generate_audio(en_text, lang2, f”audio_en_{index}.mp3″)

                        # 音声ファイルを結合する

                        japanese_audio = AudioSegment.from_file(japanese_audio_path)

                        english_audio = AudioSegment.from_file(english_audio_path)

                        silent_audio = AudioSegment.silent(duration=silence_duration) # 2秒の無音

                        combined_audio = japanese_audio + silent_audio + english_audio + silent_audio

                        combined_audio_path = os.path.join(OUTPUT_DIR, f”combined_audio_{index}.wav”)

                        combined_audio.export(combined_audio_path, format=”wav”)

                        # 音声の長さを取得

                        audio_duration = get_audio_duration(combined_audio_path)

                        # フレームレートを計算して設定

                        image_duration = (audio_duration / 2) # 各画像が表示される時間を2倍に設定

                        framerate = 1 / image_duration

                        # 画像リストファイルを作成

                        list_file_path = os.path.join(OUTPUT_DIR, f”images_list_{index}.txt”)

                        with open(list_file_path, ‘w’) as list_file:

                        list_file.write(f”file ‘{ja_text_image_path}’\n”)

                        list_file.write(f”duration {image_duration}\n”)

                        list_file.write(f”file ‘{en_screenshot_path}’\n”)

                        list_file.write(f”duration {image_duration}\n”)

                        # FFmpegコマンドを作成して実行

                        ffmpeg_cmd = [

                        “ffmpeg”,

                        “-f”, “concat”,

                        “-safe”, “0”,

                        “-i”, list_file_path, # 画像リストファイルを入力

                        “-i”, combined_audio_path, # 結合された音声ファイルのパスを入力

                        “-vf”, “scale=640:480,setsar=1”, # 画像のスケーリングとSARの設定

                        “-c:v”, “libx264”, # ビデオコーデックとしてH.264を使用する

                        “-r”, “30”, # 出力動画のフレームレートを30fpsに設定

                        “-pix_fmt”, “yuv420p”, # ピクセルフォーマットをYUV 4:2:0に設定

                        # “-shortest”, # 音声の長さに合わせて出力を終了する

                        os.path.join(OUTPUT_DIR, f”output_video_{index}.mp4″) # 出力動画ファイルのパスを指定

                        ]

                        # コマンドの実行

                        try:

                        subprocess.run(ffmpeg_cmd, check=True)

                        print(f”動画の生成が完了しました: output_video_{index}.mp4″)

                        except subprocess.CalledProcessError as e:

                        print(f”エラーが発生しました: {e}”)

                        # ドライバーを終了

                        driver.quit()

                        コメント