はじめに
映画やライブ映像の音声をサラウンドスピーカーで楽しみたいのに、手元にあるのは2chステレオのMP3ファイルしかない…というシーンはよくあると思います。
今回は Python + FFmpeg + CustomTkinter を使って、MP3音源を 5.1chサラウンド(AC-3 / Dolby Digital互換) に変換するGUIアプリを作りました。
センター・リア・LFE(サブウーファー)のミックス比率をスライダーで自由調整でき、映画・音楽・ボイス向けの3種プリセットも搭載しています。
- GitHubリポジトリ: https://github.com/amekusa03/mp3-to-ac3-converter
作ったもの
機能一覧
- 🎛️ 音響パラメータのカスタマイズ(スライダーUI)
- センター MIX 比率(0.0 〜 2.0×)
- リア / サラウンド MIX 比率(0.0 〜 2.0×)
- サラウンド ディレイ(0 〜 100 ms)
- LFE(サブウーファー)カットオフ周波数(50 〜 200 Hz)& ゲイン
- ⚡ ワンクリックプリセット 3種
- 🎥 映画・標準 (Cinema)
- 🎵 音楽・ライブ (Music Live)
- 🗣️ ボイス・対話強調 (Voice Focus)
- 📂 バッチ変換(複数ファイル・フォルダ一括追加)
- 🌙 ダーク / ライトモード切替
- 📋 リアルタイムFFmpegログモニター
5.1chアップミックスの仕組み
ステレオ(L, R)から5.1ch(FL, FR, FC, LFE, SL, SR)を生成するのに、FFmpegの filter_complex を使っています。
[0:a]asplit=5[in_flfr][in_c][in_lfe][in_sl][in_sr];
[in_flfr]pan=stereo|c0=c0|c1=c1[flfr];
[in_c]pan=1c|c0=0.707*c0+0.707*c1,volume={center_gain}[c];
[in_lfe]pan=1c|c0=0.5*c0+0.5*c1,lowpass=f={lfe_cutoff},volume={lfe_gain}[lfe];
[in_sl]pan=1c|c0=0.707*c0-0.707*c1,adelay={delay}|{delay},volume={rear_gain}[sl];
[in_sr]pan=1c|c0=-0.707*c0+0.707*c1,adelay={delay}|{delay},volume={rear_gain}[sr];
[flfr][c][lfe][sl][sr]amerge=inputs=5,pan=5.1|FL=c0|FR=c1|FC=c2|LFE=c3|BL=c4|BR=c5[out]
各チャンネルの計算式:
| チャンネル | 計算式 | 役割 |
|---|---|---|
| FL / FR | L, R そのまま | フロント左右 |
| FC(センター) | (L + R) × 0.707 × center_gain |
ボーカル・ダイアログ |
| LFE(サブウーファー) | lowpass((L + R) × 0.5) |
重低音 |
| SL(サラウンド左) | (L - R) × 0.707 × rear_gain + delay |
環境音・効果音 |
| SR(サラウンド右) | (R - L) × 0.707 × rear_gain + delay |
環境音・効果音 |
位相差(L - R)をサラウンドチャンネルに使うことで、ステレオ感を持つ音がリアスピーカーから出力されます。
コードのポイント
スレッドセーフなUI更新
Tkinter はシングルスレッドなため、バックグラウンドで動くFFmpegワーカーから直接UIを操作するとクラッシュします。queue.Queue + after() ポーリングパターンで解決しています。
# gui_app.py
def _process_queue(self):
"""Main thread polls queue every 50ms for safe UI updates."""
try:
while True:
msg_type, data = self.msg_queue.get_nowait()
if msg_type == "log":
self.append_log(data)
elif msg_type == "progress":
self.progress_bar.set(data)
# ...
except queue.Empty:
pass
finally:
self.after(50, self._process_queue) # 再スケジュール
クロージャのバグ対策(Pythonの遅延バインディング)
for ループ内でクロージャを定義すると、ループ変数 idx が常に最終値になってしまう Python の有名な罠があります。デフォルト引数でキャプチャすることで回避しています。
# ❌ バグあり: idx は常にループの最後の値になる
def _on_progress(p):
overall_p = ((idx - 1) + p) / total_files
# ✅ 修正済み: デフォルト引数で現在の値をキャプチャ
def _on_progress(p, _idx=idx, _total=total_files):
overall_p = ((_idx - 1) + p) / _total
プリセット設計(データクラス)
# presets.py
@dataclass
class AudioPreset:
name: str
description: str
icon: str
center_gain: float # 0.0 to 2.0
rear_gain: float # 0.0 to 2.0
rear_delay: int # ms
lfe_cutoff: int # Hz
lfe_gain: float # 0.0 to 2.0
bitrate: str # '448k' etc.
sample_rate: int # 48000 / 44100
インストールと起動
必要なもの
- Python 3.8+
- FFmpeg(
sudo apt install ffmpegなど)
起動方法
git clone https://github.com/[YourUsername]/mp3-to-ac3-converter
cd mp3-to-ac3-converter
./run.sh # 自動で venv 作成 & 依存インストール & 起動
まとめ
- FFmpegの
filter_complexを使えばステレオ→5.1chのアップミックスが柔軟に実現できる - CustomTkinterでモダンなダークテーマGUIを比較的シンプルに作れる
- Tkinterのスレッドセーフ問題は
queue.Queue+after()パターンが定石
プリセットのパラメータや、対応フォーマット(WAV, FLAC, M4A にも対応)はカスタマイズできるので、用途に合わせてぜひ改造してみてください!
