mirror of
https://github.com/beetbox/beets.git
synced 2025-12-27 11:02:43 +01:00
Merge pull request #1343 from jmwatte/bs1770gainsupport
Bs1770gainsupport
This commit is contained in:
commit
293e44f61b
2 changed files with 147 additions and 7 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---------------
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue