Repository: beetbox/beets

Issue:
replaygain: Sets R128 fields to zero when R128 values are not calculated
### Problem
The Replaygain plugins calculate either replaygain_* OR r128 tags, but when using "beet write", both series of tags are written, one being equal to zero.

My music player (Vanilla Music, on Android) applies r128_* gain if the tags are present, before trying the replaygain_* ones.

Is there any way NOT to write the r128 tags with the "beet write" command ? And to delete the existing r128 tags in my now messed-up library (I'm not blaming anyone ; just looking for a solution :) ) ?

### Setup

* OS: Linux
* Python version: 2.7.12
* beets version: 1.4.5

My configuration (output of `beet config`) is:

```yaml
$ beet config

paths:
    default: $artist/$album%aunique{}/$artist - $title
    singleton: $artist/$album%aunique{}/$artist - $title
    comp: $artist/$album%aunique{}/$artist - $title
threaded: yes
scrub:
    auto: no
chroma:
    auto: yes
library: ~/.config/beets/musiclibrary.db
replaygain:
    backend: bs1770gain
    method: ebu
    auto: yes
    overwrite: yes
    targetlevel: 89
    r128: [Opus]
    chunk_at: 5000

ui:
    color: yes

plugins: replaygain chroma duplicates fetchart web info
directory: ~/Musique

import:
    copy: no
    autotag: no
    resume: ask
    group_albums: yes
    bell: yes
duplicates:
    count: no
    full: no
    format: ''
    keys: []
    move: ''
    tag: ''
    path: no
    copy: ''
    tiebreak: {}
    album: no
    strict: no
    checksum: ''
    merge: no
    delete: no
fetchart:
    auto: yes
    minwidth: 0
    sources:
    - filesystem
    - coverart
    - itunes
    - amazon
    - albumart
    google_engine: 001442825323518660753:hrh5ch1gjzm
    enforce_ratio: no
    cautious: no
    maxwidth: 0
    store_source: no
    google_key: REDACTED
    fanarttv_key: REDACTED
    cover_names:
    - cover
    - front
    - art
    - album
    - folder
web:
    host: 127.0.0.1
    include_paths: no
    port: 8337
    reverse_proxy: no
    cors: ''
```



Discussion:
Hi! Hmm, I'm a little confused so far… as of 1.4.5, we're supposed to be keeping R128 and ReplayGain tags separate. The new `r128` configuration option switches from RG to R128 for specific formats (Opus only by default) and only writes R128 tags.

Any chance this dual-tag problem came from older versions of beets? If so, maybe all you need is to use `beet modify r128_album_gain=`, etc., to clear out those tags and then to run `beet replaygain` again.
As I'm fairly new to Beets (I discovered it two weeks ago), I'm pretty sure it's not a matter of previous version.
Also, `beet modify r128_album_gain=` does NOT remove the tags ; it merely sets them to zero.

Actually, Beets seems to set up r128 tags regardless of the ReplayGain plugin config ; or maybe even without that plugin enabled, and `beet write` writes them to the file, with a value of 0 (which causes my problem).
I see! So is the problem here that the R128 tags are zero (always zero) when they should be missing? Or do they sometimes have nonzero values?
Yes, that's the problem : there shouldn't be any r128 tags *except* for files specifically configured in the ReplayGain plugin config.
Here's a proof, with a new track :
```
$ beet info album:The Final Station
Oleg Sergeev, Andrey Rumak/The Final Station soundtrack/Oleg Sergeev, Andrey Rumak - Porth Wen.mp3
     album: The Final Station soundtrack
       art: True
    artist: Oleg Sergeev, Andrey Rumak
  bitdepth: 0
   bitrate: 320000
  channels: 2
      comp: False
      date: 2016-01-01
    format: MP3
     genre: Videogame
    genres: Videogame
    length: 78.2692916667
samplerate: 48000
     title: Porth Wen
     track: 1
      year: 2016
```

I then calculated the gain (for the full album, but only displayed the relevant info for one track, to avoid clutter) :
```
$ beet replaygain -a album:The Final Station
```

And here's the result :
```
$ beet info album:The Final Station         
Oleg Sergeev, Andrey Rumak/The Final Station soundtrack/Oleg Sergeev, Andrey Rumak - Porth Wen.mp3
          album: The Final Station soundtrack
    albumartist: Oleg Sergeev, Andrey Rumak
       arranger: 
            art: True
         artist: Oleg Sergeev, Andrey Rumak
       bitdepth: 0
        bitrate: 320000
            bpm: 0
       channels: 2
           comp: False
           date: 2016-01-01
           disc: 0
      disctotal: 0
         format: MP3
          genre: Videogame
         genres: Videogame
         length: 78.2692916667
         lyrics: 
     mb_trackid: 
  original_year: 0
r128_album_gain: 0
r128_track_gain: 0
  rg_album_gain: -6.18
  rg_album_peak: 1.005274
  rg_track_gain: -2.7
  rg_track_peak: 0.988433
     samplerate: 48000
          title: Porth Wen
          track: 1
     tracktotal: 0
           year: 2016
```

If I check the file's tags with VLC :
![snapshot3](https://user-images.githubusercontent.com/2071276/33957413-d9116a9a-e041-11e7-9778-3b3fc08609d3.png)

Got it; thanks! That really does clarify the issue. It sounds like the plugin is somehow writing zeroes for the R128 fields even when it's only calculating the RG values.

I don't immediately see the root cause of the problem. Doing a little more digging into where, in the plugin's workflow, the tags are being set to zero would help nail it down.
Any way I can help ?
Good question! If you know a teensy bit of Python, you could try throwing in bunch of `print(item.r128_track_gain)` statements here and there to see when the value goes from `None` (i.e., missing) to `0`.
Sadly, I don't know Python. But bash, if that helps…
Maybe this is the perfect time to learn! :smiley: :snake:
I'm the author of the R128 implementation so I've done some quick debugging today.

The *replaygain* plugin only sets the tags values by calling `ReplayGainPlugin.store_track_r128_gain()` and `ReplayGainPlugin.store_album_r128_gain()` methods respectively. I've tried importing a flac album with a similar config to the one posted by @breversa (at least as far as the *replaygain* plugin is concerned), and as described, `R128_` tags appear with 0 values **however** the methods do not get called. This leads me to believe the bug is not related to the plugin but maybe to the database part?

An easy way to test it is to change the debug messages for the *r128* methods to be different from the "standard" ones at lines 880 and 894 in `replaygain.py` (since right now they are identical) and run beets with `-v -v`. It only shows the original messages for me. I've even tried commenting out the blocks that call the *r128* methods but the tags still appeared. I'm thinking that this might actually be worth to submit as a PR, so that we get some more information in the debugging messages.

A bit of a wild guess but I have a feeling it also might be related to [this](https://github.com/beetbox/beets/pull/2560#issuecomment-301502553). (That at least explains why `beet modify r128_album_gain=` does not actually delete the tags, unless something has changed with regard to that). @sampsyo?

I'll look at this a bit more if I have the time but if anybody else decides to pick this up hopefully this information will be of some use.
Hmm, yes, it could indeed be that the Item abstraction never actually deletes these flags on the underlying MediaFile... but somehow I thought we had addressed that, so that these fields wouldn’t keep reappearing when there’s no RG analysis done. I’ll look into it too.
Glad know the experts are on the case. ;-)
Is there any progress ? :-)
I've managed to track down the issue: the tags don't have a value of `None` but `0`, that's why they don't get deleted.

I'll submit a fix as a PR.

Resolving patch:
diff --git a/beets/dbcore/types.py b/beets/dbcore/types.py
--- a/beets/dbcore/types.py
+++ b/beets/dbcore/types.py
@@ -143,6 +143,12 @@ def format(self, value):
         return u'{0:0{1}d}'.format(value or 0, self.digits)
 
 
+class NullPaddedInt(PaddedInt):
+    """Same as `PaddedInt`, but does not normalize `None` to `0.0`.
+    """
+    null = None
+
+
 class ScaledInt(Integer):
     """An integer whose formatting operation scales the number by a
     constant and adds a suffix. Good for units with large magnitudes.
diff --git a/beets/library.py b/beets/library.py
--- a/beets/library.py
+++ b/beets/library.py
@@ -474,8 +474,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),
+        'r128_track_gain':      types.NullPaddedInt(6),
+        'r128_album_gain':      types.NullPaddedInt(6),
         'original_year':        types.PaddedInt(4),
         'original_month':       types.PaddedInt(2),
         'original_day':         types.PaddedInt(2),
diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py
--- a/beetsplug/replaygain.py
+++ b/beetsplug/replaygain.py
@@ -904,7 +904,7 @@ 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)
+        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
@@ -918,7 +918,7 @@ 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)
+        self._log.debug(u'applied r128 album gain {0}', album.r128_album_gain)
 
     def handle_album(self, album, write, force=False):
         """Compute album and track replay gain store it in all of the


Test patch: