Better ReplayGain 2 / loudgain support

I think we have to start somewhere. So I’d go and tackle “MediaFile” first, to get a feel for it and provide more ReplayGain 2 stuff that loudgain has to offer.

Three questions @adrian:

  1. If any new entries are added to MediaFile, would beets automatically know about them or even possibly store them in the database (thinking of REPLAYGAIN_TRACK_RANGE, REPLAYGAIN_ALBUM_RANGE and REPLAYGAIN_REFERENCE_LOUDNESS)?

    If not, how to make that happen?

  2. For some more exotic uses, loudgain has the ability to store loudness and gain values postfixed with a “LU” unit (instead of “dB”). This is already correctly handled by IDJC and all players that simply use C’s atof() to get at the values.

    Would something like this work?

    rg_album_gain = MediaField(
        MP3DescStorageStyle(
            u'REPLAYGAIN_ALBUM_GAIN',
            float_places=2, suffix=u' dB'
        ),
        MP3DescStorageStyle(
            u'replaygain_album_gain',
            float_places=2, suffix=u' dB'
        ),
        MP3DescStorageStyle(
            u'REPLAYGAIN_ALBUM_GAIN',
            float_places=2, suffix=u' LU'
        ),
        MP3DescStorageStyle(
            u'replaygain_album_gain',
            float_places=2, suffix=u' LU'
        ),
        …

Loudness Units (LU) as specified by the EBU are equivalent to dB (1 LU = 1 dB).

  1. Concerning the reference loudness (which some programs would tag as “89 dB” and loudgain as “-18.00 LUFS”), what would beets expect? dB values, LUFS values, or would it simply not care and just handle it as an arbitrary field?

    Would a construct like the above (one for “dB”, one for “LUFS”) be able to do any calculations (dB to LUFS reference can be calculated as “dB - 107”)?

Preparing a PR, seems to work here so far.

One more question please. If I have:

    rg_track_gain = MediaField(
        MP3DescStorageStyle(
            u'REPLAYGAIN_TRACK_GAIN',
            float_places=2, suffix=u' dB'
        ),
        MP3DescStorageStyle(
            u'replaygain_track_gain',
            float_places=2, suffix=u' dB'
        ),
        MP3DescStorageStyle(
            u'REPLAYGAIN_TRACK_GAIN',
            float_places=2, suffix=u' LU'
        ),
        MP3DescStorageStyle(
            u'replaygain_track_gain',
            float_places=2, suffix=u' LU'
        ),
        …

MediaFile will apparently read all 4 variants correctly, but always write according to the last entry in the list (i.e. using “LU” units).

Am I making some dumb mistake or is that intended behaviour? Then I should probably put the most used variant (“dB”) last …

EDIT: So it seems float_places and suffix are only used for writing. Hmm. So until this can be somehow resolved (writing back the same units as read) we could assume that 99% of all people probably use “dB” and ignore the “LU” variant for now …?

Regarding reference loudness: A value of less than 0 dB SPL or more than 0 LUFS could possibly be considered nonsensical. We could then “generate” an appropriate suffix (“dB” or “LUFS”) simply by distinguishing positive (dB) from negativ (LUFS) values. Where would I put such logic in MediaFile?

Ok, done, works. Now how to get rg_track_range, rg_album_range and rg_reference into beets? (Q #1 above) :nerd_face:

Awesome; thanks for looking into this!

Nope. The way to do this is to add fields with the same names to the beets.library module. They need entries like this in the Item class definition:

And similarly for the Album class.

This is a little tricky. The way multiple storage styles work in MediaFile is with a “read any, write all” policy. But of course, it doesn’t make sense to “write all” when those values would need to be stored in the same “place.” That is, trying to write all of them would successively clobber the previously-written tag.

However, take a look at the way “suffix” works. When reading tags (cf. StorageStyle.deserialize), we just strip off the suffix if it’s there. Maybe the sensible thing to do would be to allow there to be a list of suffixes, all of which get stripped of if they’re present, rather than using separate StorageStyles for each.

This might also explain the thing you’re seeing where only one thing is being written.

Cool. As you can see in the PR, I’ve (for now) scrapped the “LU” variant since it’s really rarely used, so we can proceed and get sonething going.

SO what about database table construction? I think beets wouldn’t somehow auto-magically extend the database if I added the new fields to library.py, or does it?

If so, expect the next PR soon :grin:

It does, in fact!

Woohaa, beets impresses me again every day. Some essential deficiencies (date queries, hee hee), and mostly just super-well thought-out stuff that impresses me! Very well done.

So you’re saying if I come up with a PR for library.py, we’re set and the extra fields would be in the item table and readily available, like for “info” and queries?

Yep! All it takes is a new entry in library.py.

1 Like

So cool! I didn’t plan programming anymore today, but well … since it’s so easy, let me just jump into my office and hack a PR together … Ooops, make fresh tea first … just a sec …

Et voilà!


Really helpful you have a good CI going.

Did some more intensive testing today, works like a treat. I had to re-import the library, because update wouldn’t of course see any change.