Beets: replaygain: Add a loudgain backend

Created on 14 Sep 2019  Â·  14Comments  Â·  Source: beetbox/beets

Use case

As the current developer of loudgain, a ReplayGain 2.0 tagging tool for many file formats, I’d much like to see loudgain being supported by beets as an official ReplayGain backend.

Solution

I assume integration shouldn’t be too complicated, since loudgain uses an enhanced version of the well-known _mp3gain_ commandline syntax.

Let me know how I could help getting this going.
Any testing results from beets users and feedback on loudgain’s issue tracker welcome!

More info

loudgain on GitHub
loudgain in the Hydrogenaudio wiki

feature

Most helpful comment

No worries :) Just tried the ffmpeg backend on master and it seems to work fine, R128_ALBUM_GAIN and R128_TRACK_GAIN tags are present in the resulting files, so yeah, I'm not sure adding support for another replaygain backend is warranted.

All 14 comments

Looks cool! I am really not an expert in this space, so is there any chance you could expand a little on the advantages of your new tool vs. using FFmpeg's gain scanning module?

Good question, I never much used the FFmpeg gain scanner, albeit I do use the FFmpeg libraries to be able to easily read and decode many file types. In loudgain, RG2 calculation is done purely by using libebur128 to get at correct gain and _true peak_ values, as well as (optional) dynamic range. It also allows clipping prevention according to the EBU R128 recommendation (at -1 dBTP), and other options like writing upper- or lowercase RG tags, this unfortunately still being a problem with many players (i.e. KODI requiring lower case, VLC requiring uppercase and so on).

Oh well, and it uses an enhanced version of the mp3gain commandline syntax, never changes audio data (it only writes tags), and handles a lot of commonly used file types, using the same piece of software and commandline options.

Don’t want to make this an ad—much more tech details can be found on loudgain’s GitHub page. :-)

N.B.: The many loudgain options _might_ require some extra beets settings somewhere, like

  • does the user wish to store upper- or lowercase RG tags?
  • does the user wish to add clipping prevention (reduce gain to compensate for clipping)?
  • does the user wish to strip "foreign" tags from files?
  • should RG tags be written to the audio files or just calculated for storage into beets’ database (the -O option might come in handy)?

EDIT: Re "loudgain vs. FFmpeg gain" – I distinctly remember it wasn’t easy to set up a good filter chain with FFmpeg to do oversampling. libebur128 has a custom polyphase FIR filter to oversample up to 4x (at 192 kHz) to calculate near-perfect _true peak_ values, taking inter-sample peak into account. These values are also used by loudgain in case clipping prevention is enabled.

EDIT 2: It might be worthwhile to compare loudgain’s reults to the method beets uses (FFmpeg?) and desbma’s r128gain, especially since he’s switched back from _true peak_ to _sample_peak_. See discussion on Hydrogenaudio.

Got it. One thing you should know is that beets ReplayGain backends do not write anything to files; beets itself takes care of all the metadata. So we would not use the tagging features of loudgain, including format-specific casing and stripping existing metadata.

The ReplayGain plugin already has a noclip setting (for the mp3gain backend), so we could provide that option to the tool. For the FFmpeg backend, we also already have a true vs. sample peak config option.

EDIT: Re "loudgain vs. FFmpeg gain" – I distinctly remember it wasn’t easy to set up a good filter chain with FFmpeg to do oversampling. libebur128 has a custom polyphase FIR filter to oversample up to 4x (at 192 kHz) to calculate near-perfect _true peak_ values, taking inter-sample peak into account. These values are also used by loudgain in case clipping prevention is enabled.

I'm slightly confused here—FFmpeg uses libebur128, AFAIK, so I don't think this counts as a difference between the two backends. Unless this filter you're describing is disabled in the way FFmpeg uses the library?

EDIT 2: It might be worthwhile to compare loudgain’s reults to the method beets uses (FFmpeg?) and desbma’s r128gain, especially since he’s switched back from _true peak_ to _sample_peak_. See discussion on Hydrogenaudio.

Yes, but it's also probably worth pointing out that r128gain uses FFmpeg too.

Ah, okay. Maybe I based this on wrong assumptions—too be honest, I haven’t been using beets for a while now. I’d have to check the current FFmpeg possibilities and how you’re using it.

So, if I understand correctly, you’re saying the ReplayGain backends just _calculate_ RG and peak values and you just store these in the beets database? Since beets _can_ write tags to files, would there be any case where it actually _does_ write the ReplayGain/peak values to the files?

Another question: _If_ it turned out that loudgain should be a possible RG backend, would it help to add a JSON output to loudgain?

I’ll surely set up a beets testing environment and have a peek at the current code in the next days.

So, if I understand correctly, you’re saying the ReplayGain backends just _calculate_ RG and peak values and you just store these in the beets database? Since beets _can_ write tags to files, would there be any case where it actually _does_ write the ReplayGain/peak values to the files?

Yep! Except that beets itself writes these to the files—this is a part of "core beets," so all the plugin needs to do is calculate the values and put them in the database.

Another question: _If_ it turned out that loudgain should be a possible RG backend, would it help to add a JSON output to loudgain?

Absolutely! That would be awesome. Or CSV, if that's easier?

CSV is already in there (tab-delimited to stdout), just use the -O (capital letter "O") option.

Regarding JSON: The idea would be to output JSON data for _all_ files given on the commandline, including an entry for "Album" if album mode was specified. Does beets ask the RG backend for complete albums, a number of files, or just one file each?

Oh nice, that's cool! CSV seems just as easy to manage as JSON from Python, so maybe the JSON mode isn't necessary after all.

The plugin does indeed ask the tool to analyze entire albums (when used in album mode).

I just did a "quick one" using the same file:

$ ffmpeg -i 01\ -\ Blind\ Faith.aiff -af loudnorm=I=-18:TP=-1:LRA=11:print_format=summary -f null -
…
[Parsed_loudnorm_0 @ 0x3085300] 
Input Integrated:    -15.2 LUFS
Input True Peak:      -0.3 dBTP
Input LRA:             4.7 LU
Input Threshold:     -25.4 LUFS

Output Integrated:   -17.1 LUFS
Output True Peak:     -1.0 dBTP
Output LRA:            3.6 LU
Output Threshold:    -27.3 LUFS

Normalization Type:   Dynamic
Target Offset:        -0.9 LU
$ loudgain -k 01\ -\ Blind\ Faith.aiff 
[âś”] Scanning '01 - Blind Faith.aiff' ...
[âś”] Container: Audio IFF [aiff]
[âś”] Stream #0: PCM signed 16-bit big-endian, 16 bit, 44100 Hz, 2 ch, stereo
 100% [========================================================================]

Track: 01 - Blind Faith.aiff
 Loudness:   -15.16 LUFS
 Range:        4.75 dB
 Peak:     0.963379 (-0.32 dBTP)
 Gain:        -2.84 dB
$ loudgain -O -k 01\ -\ Blind\ Faith.aiff 
[âś”] Scanning '01 - Blind Faith.aiff' ...
[âś”] Container: Audio IFF [aiff]
[âś”] Stream #0: PCM signed 16-bit big-endian, 16 bit, 44100 Hz, 2 ch, stereo
File    Loudness    Range   True_Peak   True_Peak_dBTP  Reference   Will_clip   Clip_prevent    Gain    New_Peak    New_Peak_dBTP
01 - Blind Faith.aiff   -15.16 LUFS 4.75 dB 0.963379    -0.32 dBTP  -18.00 LUFS N   N   -2.84 dB    0.695024    -3.16 dBTP

So it seems both FFmpeg and loudgain arrive at nearly identical data (expected) but FFmpeg uses the "official" notation with just 1 decimal whereas loudgain follows the RG2 spec with 2 decimals.

So what’s to be gained using loudgain (pun not intended)?

  • a little more precision
  • an alternative in case the distro’s ffmpeg has no ebur128 or loudnorm filter

Then again, loudgain requires

  • the FFmpeg libraries
  • libebur128
  • TagLib (libtag1 or equivalent)

Self-critical question: Would that justify adding loudgain as a RG backend?

I'm trying to switch to Opus as my library format but replaygain support is holding me back. bs1770gain crashes on Archlinux with no fix in the foreseeable future, so I'm interested in implementing either a loudgain or r128gain backend. I haven't investigated both libraries in detail to have any preference, so I'm wondering if a) the beets devs have any particular preference and b) if a MR would be accepted to add support to the replaygain plugin.

I also see there is an open MR that adds new fields, wondering if someone could take a look at it. Thanks!

That PR for the new fields seems cool, although I've lost context here. Maybe you can help by weighing in on that thread about whether it makes sense to add those fields now, even if there is no MediaFile support for them nor any RG backend that makes use of them? (Or perhaps there is?) Some commentary on that thread to help us understand what needs to be done would be super helpful.

There's no particular objection to new backends, but have you tried the recently added FFmpeg backend? (Both loudgain and r128gain use FFmpeg under the hood, IIRC.)

Oh, I wasn't aware there was an ffmpeg backend. I see the commits are almost a year old, but there has been no release since. I will try master and report back, are there any plans to do a release soon?

I guess those extra fields only make sense if there is / will be a plugin backend that reports them, otherwise it's no use.

Yes, we are super behind on doing a release and hope to do that real soon now. :(

No worries :) Just tried the ffmpeg backend on master and it seems to work fine, R128_ALBUM_GAIN and R128_TRACK_GAIN tags are present in the resulting files, so yeah, I'm not sure adding support for another replaygain backend is warranted.

Was this page helpful?
0 / 5 - 0 ratings