#!/usr/bin/env python3 # Normalisation is done at the default of each program, # which is usually -89.0 dB import os import subprocess import coloredlogs import logging import progressbar coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s') log = logging.getLogger() # Constants FORCE = False # TODO UX cli argument SOURCE_FOLDER = os.path.join(os.path.expanduser("~"), "Musique") GAIN_COMMANDS = {("flac",): ["metaflac", "--add-replay-gain"], ("mp3",): ["mp3gain", "-a", "-k"] + (["-s", "r"] if FORCE else []), ("m4a", "mp4"): ["aacgain", "-a", "-k"] + (["-s", "r"] if FORCE else []), ("ogg",): ["vorbisgain", "--album"] + ([] if FORCE else ["--fast"]), } # Since metaflac ALWAYS recalculate the tags, we need to specificaly verify # that the tags are present on every track of an album to skip it def isFlacTagged(f): # TODO PERF Run metaflac --show-tag for the whole album and compare the # output with the number of files tagged for tag in ["REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_ALBUM_GAIN"]: cmd = ["metaflac", "--show-tag", tag, f] proc = subprocess.run(cmd, stdout=subprocess.PIPE) res = len(proc.stdout.strip()) > 0 if not res: return False return True # TODO UX Do the same thing with other formats so the output is not # inconsistent # Get album paths albums = set() for root, dirs, files in os.walk(SOURCE_FOLDER): relRoot = os.path.relpath(root, SOURCE_FOLDER) # See if it's an album (only 2 components in the path) head, tail = os.path.split(relRoot) if not len(head): continue head, tail = os.path.split(head) if len(head): continue albums.add(root) cmds = list() for album in albums: albumName = os.path.relpath(album, SOURCE_FOLDER) log.info("Processing album {}".format(albumName)) musicFiles = dict() for root, dirs, files in os.walk(album): for f in files: ext = os.path.splitext(f)[1][1:].lower() for exts in GAIN_COMMANDS.keys(): if ext in exts: if exts not in musicFiles.keys(): musicFiles[exts] = set() fullPath = os.path.join(root, f) musicFiles[exts].add(fullPath) if len(musicFiles) >= 2: log.warn("Different extensions for album {}. AlbumGain won't be on par.".format(albumName)) for exts, files in musicFiles.items(): if exts == ("flac",) and not FORCE: allTagged = True for f in files: if not isFlacTagged(f): allTaged = False break if allTagged: log.debug("Already tagged (for flac only)!") break cmd = GAIN_COMMANDS[exts] + list(files) log.debug("Registering command: `{}`".format(" ".join(cmd))) cmds.append(cmd) logging.info("Executing commands") for cmd in progressbar.progressbar(cmds): subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)