#!/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 . 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)