From 9fe171c5cedd1a2abd96679d89ffc159239b1985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Koutensk=C3=BD?= Date: Wed, 17 May 2017 21:37:38 +0200 Subject: [PATCH 1/4] properly safe cast unicode as int --- beets/mediafile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/beets/mediafile.py b/beets/mediafile.py index 9242ab1f1..50c2cb76b 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -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: From ddfe44266b3e2588a11884f1d3b3ba530c3800d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Koutensk=C3=BD?= Date: Wed, 17 May 2017 21:41:12 +0200 Subject: [PATCH 2/4] r128 gain tags in mediafile test --- test/test_mediafile.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 18dcc11a3..5a004b7e8 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -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 From 2685f133152c6b059ab116560c5ee357c6550404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Koutensk=C3=BD?= Date: Sat, 13 May 2017 19:50:43 +0200 Subject: [PATCH 3/4] replaygain: support r128 --- beets/library.py | 4 ++ beets/mediafile.py | 32 +++++++++++++++ beetsplug/replaygain.py | 86 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 114 insertions(+), 8 deletions(-) diff --git a/beets/library.py b/beets/library.py index 4b2b194fc..991de59e8 100644 --- a/beets/library.py +++ b/beets/library.py @@ -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', diff --git a/beets/mediafile.py b/beets/mediafile.py index 50c2cb76b..d928e05c4 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -2007,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'), diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index 4cf7da7c5..69fa8d6c1 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -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``. """ From 8168a88a7f6e401c7a982567f44d7e55e41f09d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Koutensk=C3=BD?= Date: Wed, 17 May 2017 21:58:41 +0200 Subject: [PATCH 4/4] update replaygain docs with info about r128 --- docs/plugins/replaygain.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/plugins/replaygain.rst b/docs/plugins/replaygain.rst index 6f119e2b0..838a2ea2b 100644 --- a/docs/plugins/replaygain.rst +++ b/docs/plugins/replaygain.rst @@ -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: