mirror of
https://github.com/beetbox/beets.git
synced 2025-12-25 10:05:13 +01:00
replaygain: consolidate command invocation code
Invocations of the mp3gain/aacgain commands are now wrapped in a centralized function that takes care of output capture and error handling. This avoids code duplication for the various sites at which the tool needs to be invoked. This change also avoids unintentionally modifying tags via the command-line tool. The "-s s" option makes the tool *just* calculate RG values rather than toying with tags at all.
This commit is contained in:
parent
df6c244381
commit
4adc896a86
1 changed files with 32 additions and 34 deletions
|
|
@ -31,14 +31,23 @@ log = logging.getLogger('beets')
|
|||
|
||||
DEFAULT_REFERENCE_LOUDNESS = 89
|
||||
|
||||
class RgainError(Exception):
|
||||
"""Base for exceptions in this module."""
|
||||
|
||||
class RgainNoBackendError(RgainError):
|
||||
"""The audio rgain could not be computed because neither mp3gain
|
||||
nor aacgain command-line tool is installed.
|
||||
class ReplayGainError(Exception):
|
||||
"""Raised when an error occurs during mp3gain/aacgain execution.
|
||||
"""
|
||||
|
||||
def call(args):
|
||||
"""Execute the command indicated by `args` (an array of strings) and
|
||||
return the command's output. The stderr stream is ignored. If the command
|
||||
exits abnormally, a ReplayGainError is raised.
|
||||
"""
|
||||
try:
|
||||
with open(os.devnull, 'w') as devnull:
|
||||
return subprocess.check_output(args, stderr=devnull)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise ReplayGainError(
|
||||
"{0} exited with status {1}".format(args[0], e.returncode)
|
||||
)
|
||||
|
||||
class ReplayGainPlugin(BeetsPlugin):
|
||||
'''Provides replay gain analysis for the Beets Music Manager'''
|
||||
|
||||
|
|
@ -72,11 +81,11 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
)
|
||||
else:
|
||||
# Check whether the program is in $PATH.
|
||||
for cmd in ['mp3gain','aacgain']:
|
||||
for cmd in ('mp3gain', 'aacgain'):
|
||||
try:
|
||||
subprocess.call([cmd,'-v'], stderr=subprocess.PIPE)
|
||||
call([cmd, '-v'])
|
||||
self.command = cmd
|
||||
except OSError:
|
||||
except OSError as exc:
|
||||
pass
|
||||
if not self.command:
|
||||
raise ui.UserError(
|
||||
|
|
@ -116,15 +125,8 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
|
||||
def get_recommended_gains(self, media_paths):
|
||||
'''Returns recommended track and album gain values'''
|
||||
|
||||
proc = subprocess.Popen([self.command,'-o','-d',str(self.gain_offset)] +
|
||||
media_paths,
|
||||
stdout=subprocess.PIPE)
|
||||
retcode = proc.poll()
|
||||
if retcode:
|
||||
raise RgainError("%s exited with status %i" %
|
||||
(self.command,retcode))
|
||||
rgain_out, _ = proc.communicate()
|
||||
rgain_out = call([self.command, '-o', '-d', str(self.gain_offset)] +
|
||||
media_paths)
|
||||
rgain_out = rgain_out.strip('\n').split('\n')
|
||||
keys = rgain_out[0].split('\t')[1:]
|
||||
tracks_mp3_gain = [dict(zip(keys,
|
||||
|
|
@ -160,7 +162,6 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
'''Compute replaygain taking options into account.
|
||||
Returns filtered command stdout'''
|
||||
|
||||
cmd_args = []
|
||||
media_files = [mf for mf in media_files if self.requires_gain(mf)]
|
||||
if not media_files:
|
||||
print 'No gain to compute'
|
||||
|
|
@ -174,30 +175,27 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
self.gain_offset = self.reduce_gain_for_noclip(track_gains,
|
||||
album_gain)
|
||||
|
||||
cmd = [self.command, '-o']
|
||||
# Construct shell command. The "-o" option makes the output
|
||||
# easily parseable (tab-delimited). "-s s" forces gain
|
||||
# recalculation even if tags are already present and disables
|
||||
# tag-writing; this turns the mp3gain/aacgain tool into a gain
|
||||
# calculator rather than a tag manipulator because we take care
|
||||
# of changing tags ourselves.
|
||||
cmd = [self.command, '-o', '-s', 's']
|
||||
if self.noclip:
|
||||
# Adjust to avoid clipping.
|
||||
cmd = cmd + ['-k']
|
||||
else:
|
||||
# Disable clipping warning.
|
||||
cmd = cmd + ['-c']
|
||||
if self.apply_gain:
|
||||
# Lossless audio adjustment.
|
||||
cmd = cmd + ['-r']
|
||||
cmd = cmd + ['-d', str(self.gain_offset)]
|
||||
cmd = cmd + media_paths
|
||||
|
||||
try:
|
||||
with open(os.devnull, 'w') as tempf:
|
||||
subprocess.check_call(cmd, stdout=subprocess.PIPE, stderr=tempf)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RgainError("%s exited with status %i" % (cmd, e.returncode))
|
||||
|
||||
cmd = [self.command, '-s','c','-o'] + media_paths
|
||||
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
if proc.poll():
|
||||
raise RgainError("%s exited with status %i" % (cmd, retcode))
|
||||
|
||||
tmp = proc.communicate()[0]
|
||||
return self.extract_rgain_infos(tmp)
|
||||
output = call(cmd)
|
||||
return self.extract_rgain_infos(output)
|
||||
|
||||
|
||||
def write_rgain(self, media_files, rgain_infos):
|
||||
|
|
|
|||
Loading…
Reference in a new issue