< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Plugins.MusicBrainz.MusicBrainzAlbumProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 106
Coverable lines: 106
Total lines: 250
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 80
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/13/2026 - 12:11:21 AM Line coverage: 36.5% (19/52) Branch coverage: 13.6% (3/22) Total lines: 3074/19/2026 - 12:14:27 AM Line coverage: 14.9% (19/127) Branch coverage: 3.5% (3/84) Total lines: 3075/7/2026 - 12:15:44 AM Line coverage: 0% (0/106) Branch coverage: 0% (0/80) Total lines: 250 2/13/2026 - 12:11:21 AM Line coverage: 36.5% (19/52) Branch coverage: 13.6% (3/22) Total lines: 3074/19/2026 - 12:14:27 AM Line coverage: 14.9% (19/127) Branch coverage: 3.5% (3/84) Total lines: 3075/7/2026 - 12:15:44 AM Line coverage: 0% (0/106) Branch coverage: 0% (0/80) Total lines: 250

Coverage delta

Coverage delta 22 -22

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Name()100%210%
get_Order()100%210%
GetSearchResults()0%110100%
GetReleaseSearchResult()0%2040%
GetReleaseGroupResultAsync()0%2040%
GetReleaseResult(...)0%342180%
GetMetadata()0%1980440%
GetImageResponse(...)100%210%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Net.Http;
 5using System.Runtime.CompilerServices;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using Jellyfin.Extensions;
 9using MediaBrowser.Controller.Entities.Audio;
 10using MediaBrowser.Controller.Providers;
 11using MediaBrowser.Model.Entities;
 12using MediaBrowser.Model.Providers;
 13using MediaBrowser.Providers.Music;
 14using MetaBrainz.MusicBrainz;
 15using MetaBrainz.MusicBrainz.Interfaces.Entities;
 16using MetaBrainz.MusicBrainz.Interfaces.Searches;
 17
 18namespace MediaBrowser.Providers.Plugins.MusicBrainz;
 19
 20/// <summary>
 21/// Music album metadata provider for MusicBrainz.
 22/// </summary>
 23public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder
 24{
 25    /// <inheritdoc />
 026    public string Name => "MusicBrainz";
 27
 28    /// <inheritdoc />
 029    public int Order => 0;
 30
 31    /// <inheritdoc />
 32    public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancella
 33    {
 034        var query = MusicBrainz.Plugin.Instance!.MusicBrainzQuery;
 035        var releaseId = searchInfo.GetReleaseId();
 036        var releaseGroupId = searchInfo.GetReleaseGroupId();
 37
 038        if (!string.IsNullOrEmpty(releaseId))
 39        {
 040            var releaseResult = await query.LookupReleaseAsync(new Guid(releaseId), Include.Artists | Include.ReleaseGro
 041            return GetReleaseResult(releaseResult).SingleItemAsEnumerable();
 42        }
 43
 044        if (!string.IsNullOrEmpty(releaseGroupId))
 45        {
 046            var releaseGroupResult = await query.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.Releases, nul
 47
 48            // No need to pass the cancellation token to GetReleaseGroupResultAsync as we're already passing it to ToBlo
 049            return GetReleaseGroupResultAsync(releaseGroupResult.Releases, CancellationToken.None).ToBlockingEnumerable(
 50        }
 51
 052        var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId();
 53
 054        if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
 55        {
 056            var releaseSearchResults = await query.FindReleasesAsync($"\"{searchInfo.Name}\" AND arid:{artistMusicBrainz
 057                .ConfigureAwait(false);
 58
 059            if (releaseSearchResults.Results.Count > 0)
 60            {
 061                return GetReleaseSearchResult(releaseSearchResults.Results);
 62            }
 63        }
 64        else
 65        {
 66            // I'm sure there is a better way but for now it resolves search for 12" Mixes
 067            var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal);
 68
 069            var releaseSearchResults = await query.FindReleasesAsync($"\"{queryName}\" AND artist:\"{searchInfo.GetAlbum
 070                .ConfigureAwait(false);
 71
 072            if (releaseSearchResults.Results.Count > 0)
 73            {
 074                return GetReleaseSearchResult(releaseSearchResults.Results);
 75            }
 76        }
 77
 078        return Enumerable.Empty<RemoteSearchResult>();
 079    }
 80
 81    private IEnumerable<RemoteSearchResult> GetReleaseSearchResult(IEnumerable<ISearchResult<IRelease>>? releaseSearchRe
 82    {
 083        if (releaseSearchResults is null)
 84        {
 085            yield break;
 86        }
 87
 088        foreach (var result in releaseSearchResults)
 89        {
 090            yield return GetReleaseResult(result.Item);
 91        }
 092    }
 93
 94    private async IAsyncEnumerable<RemoteSearchResult> GetReleaseGroupResultAsync(IEnumerable<IRelease>? releaseSearchRe
 95    {
 096        if (releaseSearchResults is null)
 97        {
 098            yield break;
 99        }
 100
 0101        var query = MusicBrainz.Plugin.Instance!.MusicBrainzQuery;
 0102        foreach (var result in releaseSearchResults)
 103        {
 104            // Fetch full release info, otherwise artists are missing
 0105            var fullResult = await query.LookupReleaseAsync(result.Id, Include.Artists | Include.ReleaseGroups, cancella
 0106            yield return GetReleaseResult(fullResult);
 107        }
 0108    }
 109
 110    private RemoteSearchResult GetReleaseResult(IRelease releaseSearchResult)
 111    {
 0112        var searchResult = new RemoteSearchResult
 0113        {
 0114            Name = releaseSearchResult.Title,
 0115            ProductionYear = releaseSearchResult.Date?.Year,
 0116            PremiereDate = releaseSearchResult.Date?.NearestDate,
 0117            SearchProviderName = Name
 0118        };
 119
 120        // Add artists and use first as album artist
 0121        var artists = releaseSearchResult.ArtistCredit;
 0122        if (artists is not null && artists.Count > 0)
 123        {
 0124            var artistResults = new RemoteSearchResult[artists.Count];
 0125            for (int i = 0; i < artists.Count; i++)
 126            {
 0127                var artist = artists[i];
 0128                var artistResult = new RemoteSearchResult
 0129                {
 0130                    Name = artist.Name
 0131                };
 132
 0133                if (artist.Artist?.Id is not null)
 134                {
 0135                    artistResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Artist!.Id.ToString());
 136                }
 137
 0138                artistResults[i] = artistResult;
 139            }
 140
 0141            searchResult.AlbumArtist = artistResults[0];
 0142            searchResult.Artists = artistResults;
 143        }
 144
 0145        searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString());
 146
 0147        if (releaseSearchResult.ReleaseGroup?.Id is not null)
 148        {
 0149            searchResult.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseSearchResult.ReleaseGroup.Id.ToS
 150        }
 151
 0152        return searchResult;
 153    }
 154
 155    /// <inheritdoc />
 156    public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo info, CancellationToken cancellationToken)
 157    {
 158        // TODO: This sets essentially nothing. As-is, it's mostly useless. Make it actually pull metadata and use it.
 0159        var query = MusicBrainz.Plugin.Instance!.MusicBrainzQuery;
 0160        var releaseId = info.GetReleaseId();
 0161        var releaseGroupId = info.GetReleaseGroupId();
 162
 0163        var result = new MetadataResult<MusicAlbum>
 0164        {
 0165            Item = new MusicAlbum()
 0166        };
 167
 168        // If there is a release group, but no release ID, try to match the release
 0169        if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId))
 170        {
 171            // TODO: Actually try to match the release. Simply taking the first result is stupid.
 0172            var releaseGroup = await query.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.None, null, cancell
 0173            var release = releaseGroup.Releases?.Count > 0 ? releaseGroup.Releases[0] : null;
 0174            if (release is not null)
 175            {
 0176                releaseId = release.Id.ToString();
 0177                result.HasMetadata = true;
 178            }
 179        }
 180
 181        // If there is no release ID, lookup a release with the info we have
 0182        if (string.IsNullOrWhiteSpace(releaseId))
 183        {
 0184            var artistMusicBrainzId = info.GetMusicBrainzArtistId();
 0185            IRelease? releaseResult = null;
 186
 0187            if (!string.IsNullOrEmpty(artistMusicBrainzId))
 188            {
 0189                var releaseSearchResults = await query.FindReleasesAsync($"\"{info.Name}\" AND arid:{artistMusicBrainzId
 0190                    .ConfigureAwait(false);
 0191                releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null;
 192            }
 0193            else if (!string.IsNullOrEmpty(info.GetAlbumArtist()))
 194            {
 0195                var releaseSearchResults = await query.FindReleasesAsync($"\"{info.Name}\" AND artist:{info.GetAlbumArti
 0196                    .ConfigureAwait(false);
 0197                releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null;
 198            }
 199
 0200            if (releaseResult is not null)
 201            {
 0202                releaseId = releaseResult.Id.ToString();
 203
 0204                if (releaseResult.ReleaseGroup?.Id is not null)
 205                {
 0206                    releaseGroupId = releaseResult.ReleaseGroup.Id.ToString();
 207                }
 208
 0209                result.HasMetadata = true;
 0210                result.Item.ProductionYear = releaseResult.Date?.Year;
 0211                result.Item.Overview = releaseResult.Annotation;
 212            }
 213        }
 214
 215        // If we have a release ID but not a release group ID, lookup the release group
 0216        if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId))
 217        {
 0218            var release = await query.LookupReleaseAsync(new Guid(releaseId), Include.ReleaseGroups, cancellationToken).
 0219            releaseGroupId = release.ReleaseGroup?.Id.ToString();
 0220            result.HasMetadata = true;
 221        }
 222
 223        // If we have a release ID and a release group ID
 0224        if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId))
 225        {
 0226            result.HasMetadata = true;
 227        }
 228
 0229        if (result.HasMetadata)
 230        {
 0231            if (!string.IsNullOrEmpty(releaseId))
 232            {
 0233                result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId);
 234            }
 235
 0236            if (!string.IsNullOrEmpty(releaseGroupId))
 237            {
 0238                result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId);
 239            }
 240        }
 241
 0242        return result;
 0243    }
 244
 245    /// <inheritdoc />
 246    public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
 247    {
 0248        throw new NotImplementedException();
 249    }
 250}