ffmpeg-album-normalise/normalise.py

57 lines
2.3 KiB
Python
Executable File

#!/usr/bin/env python3
# ffmpeg-album-normalise: Normalise music album-by-album using FFmpeg loudnorm
# Copyright © 2019 Lee Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import argparse
import json
import math
import os.path
import subprocess
parser = argparse.ArgumentParser(description='Normalise audio files using FFmpeg loudnorm')
parser.add_argument('filename', nargs='+', help='Input files')
parser.add_argument('--loudness', '-i', default=-24, type=float, help='Target loudness')
parser.add_argument('--outdir', '-o', default='.', help='Output directory')
parser.add_argument('--outfmt', '-t', default='wav', help='Output file format suffix')
parser.add_argument('--levels', '-l', default='levels.json', help='levels.json output file')
parser.add_argument('--args', '-a', default='', help='Additional arguments to pass to FFmpeg')
args = parser.parse_args()
with open(args.levels, 'r') as f:
levels = json.load(f)
avg_i = 0
for _, level in levels.items():
avg_i += 10**(float(level['input_i'])*10)
avg_i /= len(levels)
avg_i = math.log10(avg_i)/10
for filename in args.filename:
print(filename)
target_loudness = args.loudness + (float(levels[filename]['input_i']) - avg_i)
cmd = ['ffmpeg', '-i', filename, '-filter:a']
cmd.append('loudnorm=i={}:measured_i={}:measured_lra={}:measured_tp={}:measured_thresh={}:linear=true'.format(target_loudness, levels[filename]['input_i'], levels[filename]['input_lra'], levels[filename]['input_tp'], levels[filename]['input_thresh']))
cmd.extend(args.args.split())
cmd.append(os.path.join(args.outdir, os.path.splitext(filename)[0] + '.' + args.outfmt))
proc = subprocess.run(cmd)
if proc.returncode != 0:
print('FFmpeg error')
sys.exit(1)