Skip to content

Commit

Permalink
replaygain: support r128
Browse files Browse the repository at this point in the history
  • Loading branch information
autrimpo committed May 17, 2017
1 parent ddfe442 commit 2685f13
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 8 deletions.
4 changes: 4 additions & 0 deletions beets/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,8 @@ class Item(LibModel):
'rg_track_peak': types.NULL_FLOAT,
'rg_album_gain': types.NULL_FLOAT,
'rg_album_peak': types.NULL_FLOAT,
'r128_track_gain': types.PaddedInt(6),
'r128_album_gain': types.PaddedInt(6),
'original_year': types.PaddedInt(4),
'original_month': types.PaddedInt(2),
'original_day': types.PaddedInt(2),
Expand Down Expand Up @@ -898,6 +900,7 @@ class Album(LibModel):
'albumdisambig': types.STRING,
'rg_album_gain': types.NULL_FLOAT,
'rg_album_peak': types.NULL_FLOAT,
'r128_album_gain': types.PaddedInt(6),
'original_year': types.PaddedInt(4),
'original_month': types.PaddedInt(2),
'original_day': types.PaddedInt(2),
Expand Down Expand Up @@ -941,6 +944,7 @@ class Album(LibModel):
'albumdisambig',
'rg_album_gain',
'rg_album_peak',
'r128_album_gain',
'original_year',
'original_month',
'original_day',
Expand Down
32 changes: 32 additions & 0 deletions beets/mediafile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2007,6 +2007,38 @@ def update(self, dict):
out_type=float,
)

# EBU R128 fields.
r128_track_gain = MediaField(
MP3DescStorageStyle(
u'R128_TRACK_GAIN'
),
MP4StorageStyle(
'----:com.apple.iTunes:R128_TRACK_GAIN'
),
StorageStyle(
u'R128_TRACK_GAIN'
),
ASFStorageStyle(
u'R128_TRACK_GAIN'
),
out_type=int,
)
r128_album_gain = MediaField(
MP3DescStorageStyle(
u'R128_ALBUM_GAIN'
),
MP4StorageStyle(
'----:com.apple.iTunes:R128_ALBUM_GAIN'
),
StorageStyle(
u'R128_ALBUM_GAIN'
),
ASFStorageStyle(
u'R128_ALBUM_GAIN'
),
out_type=int,
)

initial_key = MediaField(
MP3StorageStyle('TKEY'),
MP4StorageStyle('----:com.apple.iTunes:initialkey'),
Expand Down
86 changes: 78 additions & 8 deletions beetsplug/replaygain.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ def __init__(self):
'auto': True,
'backend': u'command',
'targetlevel': 89,
'r128': ['Opus'],
})

self.overwrite = self.config['overwrite'].get(bool)
Expand All @@ -822,6 +823,9 @@ def __init__(self):
if self.config['auto']:
self.import_stages = [self.imported]

# Formats to use R128.
self.r128_whitelist = self.config['r128'].as_str_seq()

try:
self.backend_instance = self.backends[backend_name](
self.config, self._log
Expand All @@ -830,18 +834,32 @@ def __init__(self):
raise ui.UserError(
u'replaygain initialization failed: {0}'.format(e))

self.r128_backend_instance = ''

def should_use_r128(self, item):
"""Checks the plugin setting to decide whether the calculation
should be done using the EBU R128 standard and use R128_ tags instead.
"""
return item.format in self.r128_whitelist

def track_requires_gain(self, item):
return self.overwrite or \
(not item.rg_track_gain or not item.rg_track_peak)
(self.should_use_r128(item) and not item.r128_track_gain) or \
(not self.should_use_r128(item) and
(not item.rg_track_gain or not item.rg_track_peak))

def album_requires_gain(self, album):
# Skip calculating gain only when *all* files don't need
# recalculation. This way, if any file among an album's tracks
# needs recalculation, we still get an accurate album gain
# value.
return self.overwrite or \
any([not item.rg_album_gain or not item.rg_album_peak
for item in album.items()])
any([self.should_use_r128(item) and
(not item.r128_item_gain or not item.r128_album_gain)
for item in album.items()]) or \
any([not self.should_use_r128(item) and
(not item.rg_album_gain or not item.rg_album_peak)
for item in album.items()])

def store_track_gain(self, item, track_gain):
item.rg_track_gain = track_gain.gain
Expand All @@ -851,6 +869,12 @@ def store_track_gain(self, item, track_gain):
self._log.debug(u'applied track gain {0}, peak {1}',
item.rg_track_gain, item.rg_track_peak)

def store_track_r128_gain(self, item, track_gain):
item.r128_track_gain = int(round(track_gain.gain * pow(2, 8)))
item.store()

self._log.debug(u'applied 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
Expand All @@ -859,6 +883,12 @@ def store_album_gain(self, album, album_gain):
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()

self._log.debug(u'applied album gain {0}', album.r128_album_gain)

def handle_album(self, album, write):
"""Compute album and track replay gain store it in all of the
album's items.
Expand All @@ -873,17 +903,35 @@ def handle_album(self, album, write):

self._log.info(u'analyzing {0}', album)

if (any([self.should_use_r128(item) for item in album.items()]) and
all(([self.should_use_r128(item) for item in album.items()]))):
raise ReplayGainError(
u"Mix of ReplayGain and EBU R128 detected"
u"for some tracks in album {0}".format(album)
)

if any([self.should_use_r128(item) for item in album.items()]):
if self.r128_backend_instance == '':
self.init_r128_backend()
backend_instance = self.r128_backend_instance
store_track_gain = self.store_track_r128_gain
store_album_gain = self.store_album_r128_gain
else:
backend_instance = self.backend_instance
store_track_gain = self.store_track_gain
store_album_gain = self.store_album_gain

try:
album_gain = self.backend_instance.compute_album_gain(album)
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)
)

self.store_album_gain(album, album_gain.album_gain)
store_album_gain(album, album_gain.album_gain)
for item, track_gain in zip(album.items(), album_gain.track_gains):
self.store_track_gain(item, track_gain)
store_track_gain(item, track_gain)
if write:
item.try_write()
except ReplayGainError as e:
Expand All @@ -905,14 +953,23 @@ def handle_track(self, item, write):

self._log.info(u'analyzing {0}', item)

if self.should_use_r128(item):
if self.r128_backend_instance == '':
self.init_r128_backend()
backend_instance = self.r128_backend_instance
store_track_gain = self.store_track_r128_gain
else:
backend_instance = self.backend_instance
store_track_gain = self.store_track_gain

try:
track_gains = self.backend_instance.compute_track_gain([item])
track_gains = backend_instance.compute_track_gain([item])
if len(track_gains) != 1:
raise ReplayGainError(
u"ReplayGain backend failed for track {0}".format(item)
)

self.store_track_gain(item, track_gains[0])
store_track_gain(item, track_gains[0])
if write:
item.try_write()
except ReplayGainError as e:
Expand All @@ -921,6 +978,19 @@ def handle_track(self, item, write):
raise ui.UserError(
u"Fatal replay gain error: {0}".format(e))

def init_r128_backend(self):
backend_name = 'bs1770gain'

try:
self.r128_backend_instance = self.backends[backend_name](
self.config, self._log
)
except (ReplayGainError, FatalReplayGainError) as e:
raise ui.UserError(
u'replaygain initialization failed: {0}'.format(e))

self.r128_backend_instance.method = '--ebu'

def imported(self, session, task):
"""Add replay gain info to items or albums of ``task``.
"""
Expand Down

0 comments on commit 2685f13

Please sign in to comment.