Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

per_disc option for the ReplayGain plugin #3137

Merged
merged 10 commits into from
Jun 8, 2019
94 changes: 51 additions & 43 deletions beetsplug/replaygain.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __init__(self, config, log):
def compute_track_gain(self, items):
raise NotImplementedError()

def compute_album_gain(self, album):
def compute_album_gain(self, items):
# TODO: implement album gain in terms of track gain of the
# individual tracks which can be used for any backend.
raise NotImplementedError()
Expand Down Expand Up @@ -125,15 +125,14 @@ def compute_track_gain(self, items):
output = self.compute_gain(items, False)
return output

def compute_album_gain(self, album):
def compute_album_gain(self, items):
"""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)
output = self.compute_gain(items, True)

if not output:
raise ReplayGainError(u'no output from bs1770gain')
Expand Down Expand Up @@ -323,15 +322,15 @@ def compute_track_gain(self, items):
output = self.compute_gain(supported_items, False)
return output

def compute_album_gain(self, album):
def compute_album_gain(self, items):
"""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 = list(filter(self.format_supported, album.items()))
if len(supported_items) != len(album.items()):
supported_items = list(filter(self.format_supported, items))
if len(supported_items) != len(items):
self._log.debug(u'tracks are of unsupported format')
return AlbumGain(None, [])

Expand Down Expand Up @@ -528,8 +527,8 @@ def compute_track_gain(self, items):

return ret

def compute_album_gain(self, album):
items = list(album.items())
def compute_album_gain(self, items):
items = list(items)
self.compute(items, True)
if len(self._file_tags) != len(items):
raise ReplayGainError(u"Some items in album did not receive tags")
Expand Down Expand Up @@ -784,22 +783,20 @@ def _compute_track_gain(self, item):
item.artist, item.title, rg_track_gain, rg_track_peak)
return Gain(gain=rg_track_gain, peak=rg_track_peak)

def compute_album_gain(self, album):
def compute_album_gain(self, items):
"""Compute ReplayGain values for the requested album and its items.

:rtype: :class:`AlbumGain`
"""
self._log.debug(u'Analysing album {0}', album)

# The first item is taken and opened to get the sample rate to
# initialize the replaygain object. The object is used for all the
# tracks in the album to get the album values.
item = list(album.items())[0]
item = list(items)[0]
audiofile = self.open_audio_file(item)
rg = self.init_replaygain(audiofile, item)

track_gains = []
for item in album.items():
for item in items:
audiofile = self.open_audio_file(item)
rg_track_gain, rg_track_peak = self._title_gain(rg, audiofile)
track_gains.append(
Expand All @@ -812,7 +809,7 @@ def compute_album_gain(self, album):
# album values.
rg_album_gain, rg_album_peak = rg.album_gain()
self._log.debug(u'ReplayGain for album {0}: {1:.2f}, {2:.2f}',
album, rg_album_gain, rg_album_peak)
items[0].album, rg_album_gain, rg_album_peak)

return AlbumGain(
Gain(gain=rg_album_gain, peak=rg_album_peak),
Expand Down Expand Up @@ -843,9 +840,11 @@ def __init__(self):
'backend': u'command',
'targetlevel': 89,
'r128': ['Opus'],
'per_disc': False
})

self.overwrite = self.config['overwrite'].get(bool)
self.per_disc = self.config['per_disc'].get(bool)
backend_name = self.config['backend'].as_str()
if backend_name not in self.backends:
raise ui.UserError(
Expand Down Expand Up @@ -912,19 +911,18 @@ def store_track_r128_gain(self, item, track_gain):

self._log.debug(u'applied r128 track gain {0}', item.r128_track_gain)

def store_album_gain(self, album, album_gain):
album.rg_album_gain = album_gain.gain
album.rg_album_peak = album_gain.peak
album.store()

def store_album_gain(self, item, album_gain):
item.rg_album_gain = album_gain.gain
item.rg_album_peak = album_gain.peak
item.store()
self._log.debug(u'applied album gain {0}, peak {1}',
album.rg_album_gain, album.rg_album_peak)

def store_album_r128_gain(self, album, album_gain):
album.r128_album_gain = int(round(album_gain.gain * pow(2, 8)))
album.store()
item.rg_album_gain, item.rg_album_peak)

self._log.debug(u'applied r128 album gain {0}', album.r128_album_gain)
def store_album_r128_gain(self, item, album_gain):
item.r128_album_gain = album_gain.gain
item.store()
self._log.debug(u'applied r128 album gain {0}',
item.r128_album_gain)

def handle_album(self, album, write, force=False):
"""Compute album and track replay gain store it in all of the
Expand Down Expand Up @@ -958,24 +956,34 @@ def handle_album(self, album, write, force=False):
store_track_gain = self.store_track_gain
store_album_gain = self.store_album_gain

try:
album_gain = backend_instance.compute_album_gain(album)
if len(album_gain.track_gains) != len(album.items()):
raise ReplayGainError(
u"ReplayGain backend failed "
u"for some tracks in album {0}".format(album)
)
discs = dict()
if self.per_disc:
for item in album.items():
if discs.get(item.disc) is None:
discs[item.disc] = []
discs[item.disc].append(item)
else:
discs[1] = album.items()

store_album_gain(album, album_gain.album_gain)
for item, track_gain in zip(album.items(), album_gain.track_gains):
store_track_gain(item, track_gain)
if write:
item.try_write()
except ReplayGainError as e:
self._log.info(u"ReplayGain error: {0}", e)
except FatalReplayGainError as e:
raise ui.UserError(
u"Fatal replay gain error: {0}".format(e))
for discnumber, items in discs.items():
try:
album_gain = backend_instance.compute_album_gain(items)
if len(album_gain.track_gains) != len(items):
raise ReplayGainError(
u"ReplayGain backend failed "
u"for some tracks in album {0}".format(album)
)

for item, track_gain in zip(items, album_gain.track_gains):
store_track_gain(item, track_gain)
store_album_gain(item, album_gain.album_gain)
if write:
item.try_write()
except ReplayGainError as e:
self._log.info(u"ReplayGain error: {0}", e)
except FatalReplayGainError as e:
raise ui.UserError(
u"Fatal replay gain error: {0}".format(e))

def handle_track(self, item, write, force=False):
"""Compute track replay gain and store it in the item.
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ New features:
to MPD commands we don't support yet. Let us know if you find an MPD client
that doesn't get along with BPD!
:bug:`3214` :bug:`800`
* :doc:`/plugins/replaygain`: The plugin now supports a ``per_disc`` option
which enables calculation of album ReplayGain on disc level instead of album
level.
Thanks to :user:`samuelnilsson`
:bug:`293`

Fixes:

Expand Down
2 changes: 2 additions & 0 deletions docs/plugins/replaygain.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ configuration file. The available options are:
integer values instead of the common ``REPLAYGAIN_`` tags with floating point
values. Requires the "ffmpeg" backend.
Default: ``Opus``.
- **per_disc**: Calculate album ReplayGain on disc level instead of album level.
Default: ``no``

These options only work with the "command" backend:

Expand Down