mirror of
https://github.com/beetbox/beets.git
synced 2025-12-28 19:42:42 +01:00
replaygain: introduce Task objects to bundle the state related to computations
Renames *GainHandler -> *Task and instead of having a singleton instance, creates a *Task object for each album/item to process. The advantage is that now, related data can be bundled in the instance, instead of passing multiple arguments around.
This commit is contained in:
parent
ae3e95f9d3
commit
67d85d18ad
1 changed files with 118 additions and 117 deletions
|
|
@ -103,18 +103,15 @@ ALL_PEAK_METHODS = ["true", "sample"]
|
|||
Peak = enum.Enum("Peak", ["none"] + ALL_PEAK_METHODS)
|
||||
|
||||
|
||||
class GainHandler():
|
||||
def __init__(self, config, peak, log):
|
||||
self.config = config
|
||||
class RgTask():
|
||||
def __init__(self, items, album, target_level, peak, log):
|
||||
self.items = items
|
||||
self.album = album
|
||||
self.target_level = target_level
|
||||
self.peak = peak
|
||||
self._log = log
|
||||
|
||||
@property
|
||||
def target_level(self):
|
||||
"""This currently needs to reloaded from the config since the tests
|
||||
modify its value on-the-fly.
|
||||
"""
|
||||
return self.config['targetlevel'].as_number()
|
||||
self.album_gain = None
|
||||
self.track_gains = None
|
||||
|
||||
def store_track_gain(self, item, track_gain):
|
||||
item.rg_track_gain = track_gain.gain
|
||||
|
|
@ -131,18 +128,7 @@ class GainHandler():
|
|||
item.rg_album_gain, item.rg_album_peak)
|
||||
|
||||
|
||||
class R128GainHandler(GainHandler):
|
||||
def __init__(self, config, log):
|
||||
# R128_* tags do not store the track/album peak
|
||||
super().__init__(config, Peak.none, log)
|
||||
|
||||
@property
|
||||
def target_level(self):
|
||||
"""This currently needs to reloaded from the config since the tests
|
||||
modify its value on-the-fly.
|
||||
"""
|
||||
return self.config['r128_targetlevel'].as_number()
|
||||
|
||||
class R128Task(RgTask):
|
||||
def store_track_gain(self, item, track_gain):
|
||||
item.r128_track_gain = track_gain.gain
|
||||
item.store()
|
||||
|
|
@ -169,15 +155,15 @@ class Backend:
|
|||
"""
|
||||
self._log = log
|
||||
|
||||
def compute_track_gain(self, items, target_level, peak):
|
||||
"""Computes the track gain of the given tracks, returns a list
|
||||
of Gain objects.
|
||||
def compute_track_gain(self, task):
|
||||
"""Computes the track gain for the tracks belonging to `task`, and sets
|
||||
the `track_gains` attribute on the task. Returns `task`.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def compute_album_gain(self, items, target_level, peak):
|
||||
"""Computes the album gain of the given album, returns an
|
||||
AlbumGain object.
|
||||
def compute_album_gain(self, task):
|
||||
"""Computes the album gain for the album belonging to `task`, and sets
|
||||
the `album_gain` attribute on the task. Returns `task`.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -218,27 +204,28 @@ class FfmpegBackend(Backend):
|
|||
"the --enable-libebur128 configuration option is required."
|
||||
)
|
||||
|
||||
def compute_track_gain(self, items, target_level, peak):
|
||||
"""Computes the track gain of the given tracks, returns a list
|
||||
of Gain objects (the track gains).
|
||||
def compute_track_gain(self, task):
|
||||
"""Computes the track gain for the tracks belonging to `task`, and sets
|
||||
the `track_gains` attribute on the task. Returns `task`.
|
||||
"""
|
||||
gains = []
|
||||
for item in items:
|
||||
for item in task.items:
|
||||
gains.append(
|
||||
self._analyse_item(
|
||||
item,
|
||||
target_level,
|
||||
peak,
|
||||
task.target_level,
|
||||
task.peak,
|
||||
count_blocks=False,
|
||||
)[0] # take only the gain, discarding number of gating blocks
|
||||
)
|
||||
return gains
|
||||
task.track_gains = gains
|
||||
return task
|
||||
|
||||
def compute_album_gain(self, items, target_level, peak):
|
||||
"""Computes the album gain of the given album, returns an
|
||||
AlbumGain object.
|
||||
def compute_album_gain(self, task):
|
||||
"""Computes the album gain for the album belonging to `task`, and sets
|
||||
the `album_gain` attribute on the task. Returns `task`.
|
||||
"""
|
||||
target_level_lufs = db_to_lufs(target_level)
|
||||
target_level_lufs = db_to_lufs(task.target_level)
|
||||
|
||||
# analyse tracks
|
||||
# list of track Gain objects
|
||||
|
|
@ -250,9 +237,9 @@ class FfmpegBackend(Backend):
|
|||
# total number of BS.1770 gating blocks
|
||||
n_blocks = 0
|
||||
|
||||
for item in items:
|
||||
for item in task.items:
|
||||
track_gain, track_n_blocks = self._analyse_item(
|
||||
item, target_level, peak
|
||||
item, task.target_level, task.peak
|
||||
)
|
||||
track_gains.append(track_gain)
|
||||
|
||||
|
|
@ -287,10 +274,11 @@ class FfmpegBackend(Backend):
|
|||
|
||||
self._log.debug(
|
||||
"{}: gain {} LU, peak {}"
|
||||
.format(items, album_gain, album_peak)
|
||||
.format(task.items, album_gain, album_peak)
|
||||
)
|
||||
|
||||
return AlbumGain(Gain(album_gain, album_peak), track_gains)
|
||||
task.album_gain = AlbumGain(Gain(album_gain, album_peak), track_gains)
|
||||
return task
|
||||
|
||||
def _construct_cmd(self, item, peak_method):
|
||||
"""Construct the shell command to analyse items."""
|
||||
|
|
@ -466,28 +454,30 @@ class CommandBackend(Backend):
|
|||
|
||||
self.noclip = config['noclip'].get(bool)
|
||||
|
||||
def compute_track_gain(self, items, target_level, peak):
|
||||
"""Computes the track gain of the given tracks, returns a list
|
||||
of TrackGain objects.
|
||||
def compute_track_gain(self, task):
|
||||
"""Computes the track gain for the tracks belonging to `task`, and sets
|
||||
the `track_gains` attribute on the task. Returns `task`.
|
||||
"""
|
||||
supported_items = list(filter(self.format_supported, items))
|
||||
output = self.compute_gain(supported_items, target_level, False)
|
||||
return output
|
||||
supported_items = list(filter(self.format_supported, task.items))
|
||||
output = self.compute_gain(supported_items, task.target_level, False)
|
||||
task.track_gains = output
|
||||
return task
|
||||
|
||||
def compute_album_gain(self, items, target_level, peak):
|
||||
"""Computes the album gain of the given album, returns an
|
||||
AlbumGain object.
|
||||
def compute_album_gain(self, task):
|
||||
"""Computes the album gain for the album belonging to `task`, and sets
|
||||
the `album_gain` attribute on the task. Returns `task`.
|
||||
"""
|
||||
# TODO: What should be done when not all tracks in the album are
|
||||
# supported?
|
||||
|
||||
supported_items = list(filter(self.format_supported, items))
|
||||
if len(supported_items) != len(items):
|
||||
supported_items = list(filter(self.format_supported, task.items))
|
||||
if len(supported_items) != len(task.items):
|
||||
self._log.debug('tracks are of unsupported format')
|
||||
return AlbumGain(None, [])
|
||||
|
||||
output = self.compute_gain(supported_items, target_level, True)
|
||||
return AlbumGain(output[-1], output[:-1])
|
||||
output = self.compute_gain(supported_items, task.target_level, True)
|
||||
task.album_gain = AlbumGain(output[-1], output[:-1])
|
||||
return task
|
||||
|
||||
def format_supported(self, item):
|
||||
"""Checks whether the given item is supported by the selected tool.
|
||||
|
|
@ -668,21 +658,28 @@ class GStreamerBackend(Backend):
|
|||
if self._error is not None:
|
||||
raise self._error
|
||||
|
||||
def compute_track_gain(self, items, target_level, peak):
|
||||
self.compute(items, target_level, False)
|
||||
if len(self._file_tags) != len(items):
|
||||
def compute_track_gain(self, task):
|
||||
"""Computes the track gain for the tracks belonging to `task`, and sets
|
||||
the `track_gains` attribute on the task. Returns `task`.
|
||||
"""
|
||||
self.compute(task.items, task.target_level, False)
|
||||
if len(self._file_tags) != len(task.items):
|
||||
raise ReplayGainError("Some tracks did not receive tags")
|
||||
|
||||
ret = []
|
||||
for item in items:
|
||||
for item in task.items:
|
||||
ret.append(Gain(self._file_tags[item]["TRACK_GAIN"],
|
||||
self._file_tags[item]["TRACK_PEAK"]))
|
||||
|
||||
return ret
|
||||
task.track_gains = ret
|
||||
return task
|
||||
|
||||
def compute_album_gain(self, items, target_level, peak):
|
||||
items = list(items)
|
||||
self.compute(items, target_level, True)
|
||||
def compute_album_gain(self, task):
|
||||
"""Computes the album gain for the album belonging to `task`, and sets
|
||||
the `album_gain` attribute on the task. Returns `task`.
|
||||
"""
|
||||
items = list(task.items)
|
||||
self.compute(items, task.target_level, True)
|
||||
if len(self._file_tags) != len(items):
|
||||
raise ReplayGainError("Some items in album did not receive tags")
|
||||
|
||||
|
|
@ -704,7 +701,8 @@ class GStreamerBackend(Backend):
|
|||
except KeyError:
|
||||
raise ReplayGainError("results missing for album")
|
||||
|
||||
return AlbumGain(Gain(gain, peak), track_gains)
|
||||
task.album_gain = AlbumGain(Gain(gain, peak), track_gains)
|
||||
return task
|
||||
|
||||
def close(self):
|
||||
self._bus.remove_signal_watch()
|
||||
|
|
@ -897,12 +895,14 @@ class AudioToolsBackend(Backend):
|
|||
return
|
||||
return rg
|
||||
|
||||
def compute_track_gain(self, items, target_level, peak):
|
||||
"""Compute ReplayGain values for the requested items.
|
||||
|
||||
:return list: list of :class:`Gain` objects
|
||||
def compute_track_gain(self, task):
|
||||
"""Computes the track gain for the tracks belonging to `task`, and sets
|
||||
the `track_gains` attribute on the task. Returns `task`.
|
||||
"""
|
||||
return [self._compute_track_gain(item, target_level) for item in items]
|
||||
gains = [self._compute_track_gain(i, task.target_level)
|
||||
for i in task.items]
|
||||
task.track_gains = gains
|
||||
return task
|
||||
|
||||
def _with_target_level(self, gain, target_level):
|
||||
"""Return `gain` relative to `target_level`.
|
||||
|
|
@ -947,23 +947,22 @@ class AudioToolsBackend(Backend):
|
|||
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, items, target_level, peak):
|
||||
"""Compute ReplayGain values for the requested album and its items.
|
||||
|
||||
:rtype: :class:`AlbumGain`
|
||||
def compute_album_gain(self, task):
|
||||
"""Computes the album gain for the album belonging to `task`, and sets
|
||||
the `album_gain` attribute on the task. Returns `task`.
|
||||
"""
|
||||
# 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(items)[0]
|
||||
item = list(task.items)[0]
|
||||
audiofile = self.open_audio_file(item)
|
||||
rg = self.init_replaygain(audiofile, item)
|
||||
|
||||
track_gains = []
|
||||
for item in items:
|
||||
for item in task.items:
|
||||
audiofile = self.open_audio_file(item)
|
||||
rg_track_gain, rg_track_peak = self._title_gain(
|
||||
rg, audiofile, target_level
|
||||
rg, audiofile, task.target_level
|
||||
)
|
||||
track_gains.append(
|
||||
Gain(gain=rg_track_gain, peak=rg_track_peak)
|
||||
|
|
@ -974,14 +973,16 @@ class AudioToolsBackend(Backend):
|
|||
# After getting the values for all tracks, it's possible to get the
|
||||
# album values.
|
||||
rg_album_gain, rg_album_peak = rg.album_gain()
|
||||
rg_album_gain = self._with_target_level(rg_album_gain, target_level)
|
||||
rg_album_gain = self._with_target_level(
|
||||
rg_album_gain, task.target_level)
|
||||
self._log.debug('ReplayGain for album {0}: {1:.2f}, {2:.2f}',
|
||||
items[0].album, rg_album_gain, rg_album_peak)
|
||||
task.items[0].album, rg_album_gain, rg_album_peak)
|
||||
|
||||
return AlbumGain(
|
||||
task.album_gain = AlbumGain(
|
||||
Gain(gain=rg_album_gain, peak=rg_album_peak),
|
||||
track_gains=track_gains
|
||||
)
|
||||
return task
|
||||
|
||||
|
||||
class ExceptionWatcher(Thread):
|
||||
|
|
@ -1070,17 +1071,10 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
)
|
||||
)
|
||||
|
||||
# The key in this dict is the `use_r128` flag.
|
||||
self.gain_handlers = {
|
||||
True: R128GainHandler(
|
||||
self.config,
|
||||
self._log,
|
||||
),
|
||||
False: GainHandler(
|
||||
self.config,
|
||||
Peak[peak_method],
|
||||
self._log,
|
||||
),
|
||||
# The key in these dicts is the `use_r128` flag.
|
||||
self.peak_methods = {
|
||||
True: Peak.none,
|
||||
False: Peak[peak_method]
|
||||
}
|
||||
|
||||
# On-import analysis.
|
||||
|
|
@ -1150,6 +1144,22 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
|
||||
return False
|
||||
|
||||
def create_task(self, items, use_r128, album=None):
|
||||
if use_r128:
|
||||
return R128Task(
|
||||
items, album,
|
||||
self.config["r128_targetlevel"].as_number(),
|
||||
Peak.none, # R128_* tags do not store the track/album peak
|
||||
self._log,
|
||||
)
|
||||
else:
|
||||
return RgTask(
|
||||
items, album,
|
||||
self.config["targetlevel"].as_number(),
|
||||
self.peak_methods[use_r128],
|
||||
self._log,
|
||||
)
|
||||
|
||||
def handle_album(self, album, write, force=False):
|
||||
"""Compute album and track replay gain store it in all of the
|
||||
album's items.
|
||||
|
|
@ -1172,8 +1182,6 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
|
||||
self._log.info('analyzing {0}', album)
|
||||
|
||||
handler = self.gain_handlers[use_r128]
|
||||
|
||||
discs = {}
|
||||
if self.per_disc:
|
||||
for item in album.items():
|
||||
|
|
@ -1184,33 +1192,30 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
discs[1] = album.items()
|
||||
|
||||
for discnumber, items in discs.items():
|
||||
def _store_album(album_gain):
|
||||
if not album_gain or not album_gain.album_gain \
|
||||
or len(album_gain.track_gains) != len(items):
|
||||
def _store_album(task):
|
||||
if task.album_gain is None or not task.album_gain.album_gain \
|
||||
or len(task.album_gain.track_gains) != len(task.items):
|
||||
# In some cases, backends fail to produce a valid
|
||||
# `album_gain` without throwing FatalReplayGainError
|
||||
# => raise non-fatal exception & continue
|
||||
raise ReplayGainError(
|
||||
"ReplayGain backend `{}` failed "
|
||||
"for some tracks in album {}"
|
||||
.format(self.backend_name, album)
|
||||
.format(self.backend_name, task.album)
|
||||
)
|
||||
for item, track_gain in zip(items,
|
||||
album_gain.track_gains):
|
||||
handler.store_track_gain(item, track_gain)
|
||||
handler.store_album_gain(item, album_gain.album_gain)
|
||||
for item, track_gain in zip(task.items,
|
||||
task.album_gain.track_gains):
|
||||
task.store_track_gain(item, track_gain)
|
||||
task.store_album_gain(item, task.album_gain.album_gain)
|
||||
if write:
|
||||
item.try_write()
|
||||
self._log.debug('done analyzing {0}', item)
|
||||
|
||||
task = self.create_task(items, use_r128, album=album)
|
||||
try:
|
||||
self._apply(
|
||||
self.backend_instance.compute_album_gain, args=(),
|
||||
kwds={
|
||||
"items": list(items),
|
||||
"target_level": handler.target_level,
|
||||
"peak": handler.peak,
|
||||
},
|
||||
self.backend_instance.compute_album_gain,
|
||||
args=[task], kwds={},
|
||||
callback=_store_album
|
||||
)
|
||||
except ReplayGainError as e:
|
||||
|
|
@ -1231,10 +1236,9 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
return
|
||||
|
||||
use_r128 = self.should_use_r128(item)
|
||||
handler = self.gain_handlers[use_r128]
|
||||
|
||||
def _store_track(track_gains):
|
||||
if not track_gains or len(track_gains) != 1:
|
||||
def _store_track(task):
|
||||
if task.track_gains is None or len(task.track_gains) != 1:
|
||||
# In some cases, backends fail to produce a valid
|
||||
# `track_gains` without throwing FatalReplayGainError
|
||||
# => raise non-fatal exception & continue
|
||||
|
|
@ -1243,19 +1247,16 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
.format(self.backend_name, item)
|
||||
)
|
||||
|
||||
handler.store_track_gain(item, track_gains[0])
|
||||
task.store_track_gain(item, task.track_gains[0])
|
||||
if write:
|
||||
item.try_write()
|
||||
self._log.debug('done analyzing {0}', item)
|
||||
|
||||
task = self.create_task([item], use_r128)
|
||||
try:
|
||||
self._apply(
|
||||
self.backend_instance.compute_track_gain, args=(),
|
||||
kwds={
|
||||
"items": [item],
|
||||
"target_level": handler.target_level,
|
||||
"peak": handler.peak,
|
||||
},
|
||||
self.backend_instance.compute_track_gain,
|
||||
args=[task], kwds={},
|
||||
callback=_store_track
|
||||
)
|
||||
except ReplayGainError as e:
|
||||
|
|
|
|||
Loading…
Reference in a new issue