Merge pull request #1343 from jmwatte/bs1770gainsupport

Bs1770gainsupport
This commit is contained in:
Adrian Sampson 2015-03-03 10:36:00 -08:00
commit 293e44f61b
2 changed files with 147 additions and 7 deletions

View file

@ -21,6 +21,7 @@ import collections
import itertools
import sys
import warnings
import re
from beets import logging
from beets import ui
@ -32,12 +33,14 @@ from beets import config
# Utilities.
class ReplayGainError(Exception):
"""Raised when a local (to a track or an album) error occurs in one
of the backends.
"""
class FatalReplayGainError(Exception):
"""Raised when a fatal error occurs in one of the backends.
"""
@ -66,8 +69,10 @@ AlbumGain = collections.namedtuple("AlbumGain", "album_gain track_gains")
class Backend(object):
"""An abstract class representing engine for calculating RG values.
"""
def __init__(self, config, log):
"""Initialize the backend with the configuration view for the
plugin.
@ -83,10 +88,114 @@ class Backend(object):
raise NotImplementedError()
# bsg1770gain backend
class Bs1770gainBackend(Backend):
"""bs1770gain is a loudness scanner compliant with ITU-R BS.1770 and its
flavors EBU R128,ATSC A/85 and Replaygain 2.0. It uses a special
designed algorithm to normalize audio to the same level.
"""
def __init__(self, config, log):
super(Bs1770gainBackend, self).__init__(config, log)
cmd = 'bs1770gain'
try:
self.method = '--' + config['method'].get(unicode)
except:
self.method = '--replaygain'
try:
call([cmd, self.method])
self.command = cmd
except OSError:
raise FatalReplayGainError(
'Is bs1770gain installed? Is your method in config correct?'
)
if not self.command:
raise FatalReplayGainError(
'no replaygain command found: install bs1770gain'
)
def compute_track_gain(self, items):
"""Computes the track gain of the given tracks, returns a list
of TrackGain objects.
"""
output = self.compute_gain(items, False)
return output
def compute_album_gain(self, album):
"""Computes the album gain of the given album, returns an
AlbumGain object.
"""
# TODO: What should be done when not all tracks in the album are
# supported?
supported_items = album.items()
output = self.compute_gain(supported_items, True)
return AlbumGain(output[-1], output[:-1])
def compute_gain(self, items, is_album):
"""Computes the track or album gain of a list of items, returns
a list of TrackGain objects.
When computing album gain, the last TrackGain object returned is
the album gain
"""
if len(items) == 0:
return []
"""Compute ReplayGain values and return a list of results
dictionaries as given by `parse_tool_output`.
"""
# Construct shell command.
cmd = [self.command]
cmd = cmd + [self.method]
cmd = cmd + ['-it']
cmd = cmd + [syspath(i.path) for i in items]
self._log.debug(u'analyzing {0} files', len(items))
self._log.debug(u"executing {0}", " ".join(map(displayable_path, cmd)))
output = call(cmd)
self._log.debug(u'analysis finished')
results = self.parse_tool_output(output,
len(items) + is_album)
return results
def parse_tool_output(self, text, num_lines):
"""Given the output from bs1770gain, parse the text and
return a list of dictionaries
containing information about each analyzed file.
"""
out = []
data = text.decode('utf8', errors='ignore')
regex = ("(\s{2,2}\[\d+\/\d+\].*?|\[ALBUM\].*?)(?=\s{2,2}\[\d+\/\d+\]"
"|\s{2,2}\[ALBUM\]:|done\.$)")
results = re.findall(regex, data, re.S | re.M)
for ll in results[0:num_lines]:
parts = ll.split(b'\n')
if len(parts) == 0:
self._log.debug(u'bad tool output: {0!r}', text)
raise ReplayGainError('bs1770gain failed')
d = {
'file': parts[0],
'gain': float((parts[1].split('/'))[1].split('LU')[0]),
'peak': float(parts[2].split('/')[1]),
}
self._log.info('analysed {}gain={};peak={}',
d['file'].rstrip(), d['gain'], d['peak'])
out.append(Gain(d['gain'], d['peak']))
return out
# mpgain/aacgain CLI tool backend.
class CommandBackend(Backend):
def __init__(self, config, log):
super(CommandBackend, self).__init__(config, log)
config.add({
@ -218,6 +327,7 @@ class CommandBackend(Backend):
# GStreamer-based backend.
class GStreamerBackend(Backend):
def __init__(self, config, log):
super(GStreamerBackend, self).__init__(config, log)
self._import_gst()
@ -466,10 +576,12 @@ class GStreamerBackend(Backend):
class AudioToolsBackend(Backend):
"""ReplayGain backend that uses `Python Audio Tools
<http://audiotools.sourceforge.net/>`_ and its capabilities to read more
file formats and compute ReplayGain values using it replaygain module.
"""
def __init__(self, config, log):
super(AudioToolsBackend, self).__init__(config, log)
self._import_audiotools()
@ -594,13 +706,15 @@ class AudioToolsBackend(Backend):
# Main plugin logic.
class ReplayGainPlugin(BeetsPlugin):
"""Provides ReplayGain analysis.
"""
backends = {
"command": CommandBackend,
"command": CommandBackend,
"gstreamer": GStreamerBackend,
"audiotools": AudioToolsBackend
"audiotools": AudioToolsBackend,
"bs1770gain": Bs1770gainBackend
}
def __init__(self):
@ -688,6 +802,7 @@ class ReplayGainPlugin(BeetsPlugin):
)
self.store_album_gain(album, album_gain.album_gain)
for item, track_gain in itertools.izip(album.items(),
album_gain.track_gains):
self.store_track_gain(item, track_gain)

View file

@ -10,9 +10,9 @@ playback levels.
Installation
------------
This plugin can use one of three backends to compute the ReplayGain values:
GStreamer, mp3gain (and its cousin, aacgain), and Python Audio Tools. mp3gain
can be easier to install but GStreamer and Audio Tools support more audio
This plugin can use one of four backends to compute the ReplayGain values:
GStreamer, mp3gain (and its cousin, aacgain), Python Audio Tools and bs1770gain. mp3gain
can be easier to install but GStreamer, Audio Tools and bs1770gain support more audio
formats.
Once installed, this plugin analyzes all files during the import process. This
@ -75,6 +75,22 @@ On OS X, most of the dependencies can be installed with `Homebrew`_::
.. _Python Audio Tools: http://audiotools.sourceforge.net
bs1770gain
``````````
In order to use this backend, you will need to install the bs1770gain command-line tool. Here are some hints:
* goto `bs1770gain`_ and follow the download instructions
* make sure it is in your $PATH
.. _bs1770gain: bs1770gain.sourceforge.net
Then, enable the plugin (see :ref:`using-plugins`) and specify the
backend in your configuration file::
replaygain:
backend: bs1770gain
Configuration
-------------
@ -100,6 +116,15 @@ These options only work with the "command" backend:
would keep clipping from occurring.
Default: ``yes``.
This option only works with the "bs1770gain" backend:
- **method**: The loudness scanning standard: either 'replaygain' for ReplayGain 2.0,
'ebu' for EBU R128 or 'atsc' for ATSC A/85.
This dictates the reference level: -18, -23, or -24 LUFS respectively. Default: replaygain
Manual Analysis
---------------