😎
ffmpeg を使用して mp4 ファイルの音量をノーマライズ
環境
- MacBook Air M2, 2022
- macOS Sonoma 14.5
- ffmpeg version 7.0.1
- Python 3.12.4
仕様
- Python スクリプト
- 起動時引数に対象ファイル名を指定
- 変換後のファイル名の後ろに "_normalized" を付けて区別
- ffmpeg の volumedetect を使用して最大音量を取得
- 取得した最大音量を元に -0.1dB になるよう補正値を計算
- ffmpeg の volume 指定で全体の音量を均等にノーマライズ
- ffmpeg の dynaudnorm 指定は行わない
実装
#!/usr/bin/env python3
import subprocess
import sys
import re
import time
def run_command(command):
process = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
output_lines = []
while True:
output = process.stdout.readline()
error = process.stderr.readline()
if output == '' and error == '' and process.poll() is not None:
break
if output:
print(output.strip())
output_lines.append(output.strip())
if error:
print(error.strip(), file=sys.stderr)
output_lines.append(error.strip())
return '\n'.join(output_lines)
def normalize_volume(input_file):
# FFmpegコマンドを使用して音声のボリュームを検出
detect_command = ['ffmpeg', '-i', input_file, '-vn', '-af', 'volumedetect', '-f', 'null', '-']
print(f"Running command: {' '.join(detect_command)}")
start_time = time.time()
result = run_command(detect_command)
end_time = time.time()
detect_duration = end_time - start_time
print(f"Volume detection took {detect_duration:.2f} seconds")
# 出力から最大ボリュームを抽出
max_volume = None
for line in result.split('\n'):
if 'max_volume' in line:
max_volume = re.search(r'max_volume: ([\-\d\.]+) dB', line).group(1)
break
if max_volume is None:
print("Error: Could not find max_volume in ffmpeg output")
return
max_volume = float(max_volume)
print(f"Detected max volume: {max_volume} dB")
# 新しいボリュームの計算 (-0.1dBになるように)
volume_adjustment = -0.1 - max_volume
print(f"Calculated volume adjustment: {volume_adjustment} dB")
# 出力ファイル名を生成
output_file = input_file.rsplit('.', 1)[0] + '_normalized.mp4'
# FFmpegコマンドを使用してボリュームを調整
normalize_command = ['ffmpeg', '-loglevel', 'warning', '-i', input_file, '-af', f'volume={volume_adjustment}dB', '-codec:v', 'copy', output_file]
print(f"Running command: {' '.join(normalize_command)}")
start_time = time.time()
run_command(normalize_command)
end_time = time.time()
normalize_duration = end_time - start_time
print(f"Volume normalization took {normalize_duration:.2f} seconds")
print(f"Volume normalized and saved to {output_file}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python normalize_volume.py <input_file>")
else:
input_file = sys.argv[1]
normalize_volume(input_file)
余談
当初 dynaudnorm を指定して使用していましたが、ホワイトノイズが不自然に増幅されてしまうため、この方法に落ち着きました。
Discussion