mirror of
https://github.com/beetbox/beets.git
synced 2026-01-03 22:42:44 +01:00
Merge pull request #2560 from autrimpo/r128
RFC: replaygain: R128 support
This commit is contained in:
commit
0e47095bb0
5 changed files with 127 additions and 10 deletions
|
|
@ -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),
|
||||
|
|
@ -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),
|
||||
|
|
@ -941,6 +944,7 @@ class Album(LibModel):
|
|||
'albumdisambig',
|
||||
'rg_album_gain',
|
||||
'rg_album_peak',
|
||||
'r128_album_gain',
|
||||
'original_year',
|
||||
'original_month',
|
||||
'original_day',
|
||||
|
|
|
|||
|
|
@ -154,10 +154,12 @@ def _safe_cast(out_type, val):
|
|||
return int(val)
|
||||
else:
|
||||
# Process any other type as a string.
|
||||
if not isinstance(val, six.string_types):
|
||||
if isinstance(val, bytes):
|
||||
val = val.decode('utf-8', 'ignore')
|
||||
elif not isinstance(val, six.string_types):
|
||||
val = six.text_type(val)
|
||||
# Get a number from the front of the string.
|
||||
val = re.match(r'[0-9]*', val.strip()).group(0)
|
||||
val = re.match(r'[\+-]?[0-9]*', val.strip()).group(0)
|
||||
if not val:
|
||||
return 0
|
||||
else:
|
||||
|
|
@ -2005,6 +2007,38 @@ class MediaFile(object):
|
|||
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'),
|
||||
|
|
|
|||
|
|
@ -805,6 +805,7 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
'auto': True,
|
||||
'backend': u'command',
|
||||
'targetlevel': 89,
|
||||
'r128': ['Opus'],
|
||||
})
|
||||
|
||||
self.overwrite = self.config['overwrite'].get(bool)
|
||||
|
|
@ -822,6 +823,9 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
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
|
||||
|
|
@ -830,9 +834,19 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
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
|
||||
|
|
@ -840,8 +854,12 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
# 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
|
||||
|
|
@ -851,6 +869,12 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
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
|
||||
|
|
@ -859,6 +883,12 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
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.
|
||||
|
|
@ -873,17 +903,35 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
|
||||
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:
|
||||
|
|
@ -905,14 +953,23 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
|
||||
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:
|
||||
|
|
@ -921,6 +978,19 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
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``.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ configuration file. The available options are:
|
|||
Default: ``no``.
|
||||
- **targetlevel**: A number of decibels for the target loudness level.
|
||||
Default: 89.
|
||||
- **r128**: A space separated list of formats that will use ``R128_`` tags with
|
||||
integer values instead of the common ``REPLAYGAIN_`` tags with floating point
|
||||
values. Requires the "bs1770gain" backend.
|
||||
Default: ``Opus``.
|
||||
|
||||
These options only work with the "command" backend:
|
||||
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
|
|||
'rg_track_gain',
|
||||
'rg_album_peak',
|
||||
'rg_album_gain',
|
||||
'r128_track_gain',
|
||||
'r128_album_gain',
|
||||
'albumartist',
|
||||
'mb_albumartistid',
|
||||
'artist_sort',
|
||||
|
|
@ -672,6 +674,9 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
|
|||
if key.startswith('rg_'):
|
||||
# ReplayGain is float
|
||||
tags[key] = 1.0
|
||||
elif key.startswith('r128_'):
|
||||
# R128 is int
|
||||
tags[key] = -1
|
||||
else:
|
||||
tags[key] = 'value\u2010%s' % key
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue