Skip to content

Parse ReplayGain album gain field - #17315

Merged
crobibero merged 3 commits into
jellyfin:masterfrom
Florin-Popescu:replaygain-missing-tags
Aug 5, 2026
Merged

crobibero merged 3 commits into
jellyfin:masterfrom
Florin-Popescu:replaygain-missing-tags

Conversation

@Florin-Popescu

@Florin-Popescu Florin-Popescu commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Hello! First time contributor here.

I'm missing proper support for ReplayGain tags for volume normalization in Music Assistant. This PR parses the ReplayGain tags internally if present in the music files and exposes them on the server API to be usable by 3rd party clients such as Music Assistant. The following tags are parsed if available in music files:

  • REPLAYGAIN_TRACK_GAIN
    • was already handled in the code base, I didn't change it
  • REPLAYGAIN_ALBUM_GAIN
    • covers the measured loudness over an entire album. Here I chose to add it to the parent album of a track when parsed, since it's a tag in the track files themselves
    • I chose to only set it once for the parent album if the album doesn't have its NormalizationGain property already set. Normally all tracks in an album should have this tag set to the same value if the files are coming from the same source, so this implementation seems sensible to me vs. for example averaging the REPLAYGAIN_ALBUM_GAIN for all tracks in an album and setting that as the album's NormalizationGain
    • It is exposed by the server API using the field AlbumNormalizationGain when querying for a certain track as well as by the NormalizationGain field when querying for an album. The support for AlbumNormalizationGain field for a track was added in Add AlbumNormalizationGain field to BaseItemDto #14745 but to my understanding the album gain was internally populated (and therefore exposed by the API) only from jellyfin's own LUFS measurement, but not from a file's ReplayGain tags.
  • REPLAYGAIN_REFERENCE_LOUDNESS
    • we can't always assume ReplayGain was ran with its default target loudness of -18 LUFS nor can clients make such assumptions. Music Assistant for example uses -17 LUFS by default for playback. Clients need this information to play the files at the correct volume
    • this required adding a new ReferenceLoudness field in the entity database. Hope I did it correctly, including the migration file.
    • I don't know if it's really necessary, but depending on how the clients parse the library, I thought it useful to add this information to a track's parent album in addition to the track itself. The gain information by itself is not very useful without the reference LUFS for either a track or an album. It's added in the same way as REPLAYGAIN_ALBUM_GAIN: only once if not already present in the album's properties, for the first track parsed for that album. Assumption is that all files were processed to add ReplayGain tags in an album using the same settings.
    • I'm exposing the ReferenceLoudness field in the DTO API only if NormalizationGain is also present. I thought it wouldn't be useful if only the reference loudness were present in a file but not the actual gain values. That would anyway not make much sense (corrupted tags in the files?) but it doesn't hurt to protect for.

Code assistance

I'm normally a developer from the embedded world, so my C# experience is limited. I used an LLM to understand the code base, where to plug my changes, but I did the actual implementation and debugging. Tested and confirmed it works on my machine, not just AI slop.

Issues

This change just exposes the information but doesn't really fix anything by itself. I'm submitting related PRs to Music Assistant to parse this information:
Jc2k/aiojellyfin#10
music-assistant/server#4784
I haven't found any issues related to ReplayGain or volume normalization in this repo.


if (float.TryParse(trackAlbumGainTag, NumberStyles.Float, CultureInfo.InvariantCulture, out var value) && float.IsFinite(value))
{
if (!audio.AlbumEntity.NormalizationGain.HasValue)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible nullref here. AlbumEntity is FindParent<MusicAlbum>(), which returns null when the track has no MusicAlbum parent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 605de14

if (float.TryParse(trackReferenceLoudnessTag, NumberStyles.Float, CultureInfo.InvariantCulture, out var value) && float.IsFinite(value))
{
audio.ReferenceLoudness = value;
if (!audio.AlbumEntity.ReferenceLoudness.HasValue)

@dfederm dfederm Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nullref

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer relevant if not parsing the reference loudness


if (trackReferenceLoudnessTag is not null)
{
if (trackReferenceLoudnessTag.EndsWith("LUFS", StringComparison.OrdinalIgnoreCase))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider stripping "db" like with the gain. Not sure though how common that is in practice.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure either, but it definitely doesn't hurt. Is it ok to do the 2 checks separately like this? I assume you could have either "LUFS" or "dB" but not both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer relevant if not parsing the reference loudness

@jellyfin-bot

This comment was marked as off-topic.

@Bond-009

Copy link
Copy Markdown
Member

REPLAYGAIN_REFERENCE_LOUDNESS should not be used as it deviates from the spec
https://wiki.hydrogenaudio.org/index.php?title=Revised_ReplayGain_specification#De-Facto_extensions

@Florin-Popescu

Florin-Popescu commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor Author

REPLAYGAIN_REFERENCE_LOUDNESS should not be used as it deviates from the spec https://wiki.hydrogenaudio.org/index.php?title=Revised_ReplayGain_specification#De-Facto_extensions

I understand your point and while I'm personally anyway using the default -18 value, I also don't see the harm in processing it as long as it's available in the file. A file that has a track gain tag of -6 dB at -18 reference will have the track gain tag at -2 dB if it was scanned at -14 reference. If you parse only the -2dB tag without considering it was relative to a non-standard value, the file will be played too loud. If only some albums in your collection used the non-standard reference for whatever reason, but not the entire library, that defeats the purpose of volume normalization.

What should we do in this situation? Process the reference gain tag when present, translate the track/album gain internally to -18 and only expose the adjusted gain relative to -18 LUFS? I think that might be a good alternative. But ignoring the tag when present in files will cause inconsistent playback volume.

@Bond-009

Copy link
Copy Markdown
Member

But ignoring the tag when present in files will cause inconsistent playback volume.

Correct, but I see that as user error.
It's not our fault if files are tagged incorrectly.

@Florin-Popescu
Florin-Popescu force-pushed the replaygain-missing-tags branch from ede6d0d to 605de14 Compare July 24, 2026 22:16
@Florin-Popescu Florin-Popescu changed the title Parse ReplayGain reference loudness & album gain fields Parse ReplayGain album gain field Jul 24, 2026
@Florin-Popescu

Florin-Popescu commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Correct, but I see that as user error. It's not our fault if files are tagged incorrectly.

OK, removed the reference gain. PR greatly simplified due to this.
Should only assign the album gain to the parent album internally and then rely on #14745 to expose the album gain when querying for a track.
Will update music-assistant/server#4784 to assume -18 dB reference level.

@Bond-009 Bond-009 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this is way simpler

Comment thread CONTRIBUTORS.md Outdated
Comment thread MediaBrowser.Providers/MediaInfo/AudioFileProber.cs Outdated
@Florin-Popescu
Florin-Popescu requested a review from Bond-009 July 25, 2026 19:58
@Florin-Popescu
Florin-Popescu force-pushed the replaygain-missing-tags branch from 605de14 to 144c120 Compare July 28, 2026 17:43
@jellyfin-bot jellyfin-bot added the merge conflict Merge conflicts should be resolved before a merge label Jul 28, 2026
@jellyfin-bot

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.

@Florin-Popescu
Florin-Popescu force-pushed the replaygain-missing-tags branch from c820a94 to f6f0fed Compare July 28, 2026 17:54
@jellyfin-bot jellyfin-bot removed the merge conflict Merge conflicts should be resolved before a merge label Jul 28, 2026
@sonarqubecloud

Copy link
Copy Markdown

@Bond-009 Bond-009 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you

@crobibero
crobibero merged commit cd5962d into jellyfin:master Aug 5, 2026
19 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Jellyfin v12 Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants