😎

ffmpeg を使用して mp4 ファイルの音量をノーマライズ

2024/07/07に公開

環境

  • 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