diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index 4a0ea064f1..4cc5f435c5 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -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() @@ -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') @@ -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, []) @@ -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") @@ -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( @@ -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), @@ -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( @@ -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 @@ -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. diff --git a/docs/changelog.rst b/docs/changelog.rst index 264a821070..baef715084 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -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: diff --git a/docs/plugins/replaygain.rst b/docs/plugins/replaygain.rst index 728f1846ea..57630f1d60 100644 --- a/docs/plugins/replaygain.rst +++ b/docs/plugins/replaygain.rst @@ -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: