暗記用フラッシュカード動画を自動生成する学習プログラムの解説
この記事では、TSVファイルからテキストを読み込み、それに対応する画像と音声を自動で組み合わせてMP4形式のフラッシュカード動画を生成するPythonプログラムについて解説します。
プログラムの概要
このプログラムは、以下の主要な処理を自動でおこないます。
-
- TSVファイルの読み込み: あらかじめ用意したTSVファイル(タブ区切りテキスト)から、日本語と英語の単語やフレーズを読み込みます。
-
- 画像の自動生成:
-
- 日本語: 読み込んだ日本語テキストを画像に変換します。
-
- 英語: 読み込んだ英語テキストを検索クエリとしてGoogle画像検索をおこない、そのスクリーンショットを取得します。
-
- 画像の自動生成:
-
- 音声の自動生成と結合:
-
- 日本語と英語: それぞれのテキストを音声に変換します。
-
- 音声の結合: 日本語音声、無音、英語音声、無音の順に音声を結合します。
-
- 音声の自動生成と結合:
-
- 動画の自動生成: 生成した画像と結合した音声を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のインストール
pydub
やsubprocess
で音声・動画を処理するために、FFmpegという外部ツールが必要です。Pythonライブラリではないため、別途インストールする必要があります。
-
- Windowsの場合:
-
- FFmpeg公式サイトからファイルをダウンロードし、解凍します。
-
- 解凍したフォルダ内の
bin
フォルダにあるffmpeg.exe
のパスを環境変数Pathに追加してください。
- 解凍したフォルダ内の
-
- パスの追加方法が不明な場合は、「Windows 環境変数 Path 追加」で検索すると詳しい情報が見つかります。
-
- Windowsの場合:
-
- macOSの場合:
- Homebrewを使っている場合は、以下のコマンドで簡単にインストールできます。
Bash
brew install ffmpeg
- macOSの場合:
-
- Linuxの場合:
- 各ディストリビューションのパッケージマネージャーを使用します。
Bash
# Debian/Ubuntu系 sudo apt update sudo apt install ffmpeg # Fedora系 sudo dnf install ffmpeg
- Linuxの場合:
3. Chromeブラウザのインストール
Seleniumとwebdriver-manager
はChromeブラウザを操作するために使用されるため、PCにGoogle Chromeがインストールされている必要があります。まだインストールしていない場合は、Google Chromeの公式サイトからダウンロードしてインストールしてください。
プログラムの解説
提供されたコードは、大きく分けて以下の6つのセクションで構成されています。
1. 初期設定と準備
-
- ライブラリのインポート: プログラムの実行に必要なライブラリを読み込みます。
-
- パラメータ設定: 生成されたファイル(画像、音声、動画)を保存する出力ディレクトリ
OUTPUT_DIR
や、読み込むTSVファイルのパスTSV_FILE
、言語設定lang1
・lang2
、無音時間silence_duration
などを設定します。
- パラメータ設定: 生成されたファイル(画像、音声、動画)を保存する出力ディレクトリ
-
- Chromeドライバー設定関数 (
set_driver
): Selenium WebDriverを使ってChromeブラウザを操作するための設定をおこないます。--headless
オプションにより、ブラウザは画面に表示されずにバックグラウンドで動作します。
- Chromeドライバー設定関数 (
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()
コメント