< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Plugins.MusicBrainz.MusicBrainzArtistProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
Line coverage
26%
Covered lines: 19
Uncovered lines: 52
Coverable lines: 71
Total lines: 190
Line coverage: 26.7%
Branch coverage
9%
Covered branches: 3
Total branches: 32
Branch coverage: 9.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 54.2% (19/35) Branch coverage: 25% (3/12) Total lines: 1864/15/2026 - 12:14:34 AM Line coverage: 52.7% (19/36) Branch coverage: 25% (3/12) Total lines: 1904/19/2026 - 12:14:27 AM Line coverage: 26.7% (19/71) Branch coverage: 9.3% (3/32) Total lines: 190 1/23/2026 - 12:11:06 AM Line coverage: 54.2% (19/35) Branch coverage: 25% (3/12) Total lines: 1864/15/2026 - 12:14:34 AM Line coverage: 52.7% (19/36) Branch coverage: 25% (3/12) Total lines: 1904/19/2026 - 12:14:27 AM Line coverage: 26.7% (19/71) Branch coverage: 9.3% (3/32) Total lines: 190

Coverage delta

Coverage delta 26 -26

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Name()100%210%
get_Order()100%210%
ReloadConfig(...)50%2261.53%
GetSearchResults()0%7280%
GetResultsFromResponse()0%2040%
GetResultFromResponse(...)0%7280%
GetMetadata()0%7280%
GetImageResponse(...)100%210%
Dispose()100%11100%
Dispose(...)100%22100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Net.Http;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using Jellyfin.Extensions;
 8using MediaBrowser.Controller.Entities.Audio;
 9using MediaBrowser.Controller.Providers;
 10using MediaBrowser.Model.Entities;
 11using MediaBrowser.Model.Plugins;
 12using MediaBrowser.Model.Providers;
 13using MediaBrowser.Providers.Music;
 14using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
 15using MetaBrainz.MusicBrainz;
 16using MetaBrainz.MusicBrainz.Interfaces.Entities;
 17using MetaBrainz.MusicBrainz.Interfaces.Searches;
 18using Microsoft.Extensions.Logging;
 19
 20namespace MediaBrowser.Providers.Plugins.MusicBrainz;
 21
 22/// <summary>
 23/// MusicBrainz artist provider.
 24/// </summary>
 25public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable, IHasOrder
 26{
 27    private readonly ILogger<MusicBrainzArtistProvider> _logger;
 28    private Query _musicBrainzQuery;
 29
 30    /// <summary>
 31    /// Initializes a new instance of the <see cref="MusicBrainzArtistProvider"/> class.
 32    /// </summary>
 33    /// <param name="logger">The logger.</param>
 34    public MusicBrainzArtistProvider(ILogger<MusicBrainzArtistProvider> logger)
 35    {
 2136        _logger = logger;
 2137        _musicBrainzQuery = new Query();
 2138        ReloadConfig(null, MusicBrainz.Plugin.Instance!.Configuration);
 2139        MusicBrainz.Plugin.Instance!.ConfigurationChanged += ReloadConfig;
 2140    }
 41
 42    /// <inheritdoc />
 043    public string Name => "MusicBrainz";
 44
 45    /// <inheritdoc />
 46    /// Runs first to populate the MusicBrainz artist ID used by downstream providers.
 047    public int Order => 0;
 48
 49    private void ReloadConfig(object? sender, BasePluginConfiguration e)
 50    {
 2151        var configuration = (PluginConfiguration)e;
 2152        if (Uri.TryCreate(configuration.Server, UriKind.Absolute, out var server))
 53        {
 2154            Query.DefaultServer = server.DnsSafeHost;
 2155            Query.DefaultPort = server.Port;
 2156            Query.DefaultUrlScheme = server.Scheme;
 57        }
 58        else
 59        {
 60            // Fallback to official server
 061            _logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
 062            var defaultServer = new Uri(PluginConfiguration.DefaultServer);
 063            Query.DefaultServer = defaultServer.Host;
 064            Query.DefaultPort = defaultServer.Port;
 065            Query.DefaultUrlScheme = defaultServer.Scheme;
 66        }
 67
 2168        Query.DelayBetweenRequests = configuration.RateLimit;
 2169        _musicBrainzQuery = new Query();
 2170    }
 71
 72    /// <inheritdoc />
 73    public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancell
 74    {
 075        var artistId = searchInfo.GetMusicBrainzArtistId();
 76
 077        if (!string.IsNullOrWhiteSpace(artistId))
 78        {
 079            var artistResult = await _musicBrainzQuery.LookupArtistAsync(new Guid(artistId), Include.Aliases, null, null
 080            return GetResultFromResponse(artistResult).SingleItemAsEnumerable();
 81        }
 82
 083        var artistSearchResults = await _musicBrainzQuery.FindArtistsAsync($"\"{searchInfo.Name}\"", null, null, false, 
 084            .ConfigureAwait(false);
 085        if (artistSearchResults.Results.Count > 0)
 86        {
 087            return GetResultsFromResponse(artistSearchResults.Results);
 88        }
 89
 090        if (searchInfo.Name.HasDiacritics())
 91        {
 92            // Try again using the search with an accented characters query
 093            var artistAccentsSearchResults = await _musicBrainzQuery.FindArtistsAsync($"artistaccent:\"{searchInfo.Name}
 094                .ConfigureAwait(false);
 095            if (artistAccentsSearchResults.Results.Count > 0)
 96            {
 097                return GetResultsFromResponse(artistAccentsSearchResults.Results);
 98            }
 99        }
 100
 0101        return Enumerable.Empty<RemoteSearchResult>();
 0102    }
 103
 104    private IEnumerable<RemoteSearchResult> GetResultsFromResponse(IEnumerable<ISearchResult<IArtist>>? releaseSearchRes
 105    {
 0106        if (releaseSearchResults is null)
 107        {
 0108            yield break;
 109        }
 110
 0111        foreach (var result in releaseSearchResults)
 112        {
 0113            yield return GetResultFromResponse(result.Item);
 114        }
 0115    }
 116
 117    private RemoteSearchResult GetResultFromResponse(IArtist artist)
 118    {
 0119        var searchResult = new RemoteSearchResult
 0120        {
 0121            Name = artist.Name,
 0122            ProductionYear = artist.LifeSpan?.Begin?.Year,
 0123            PremiereDate = artist.LifeSpan?.Begin?.NearestDate,
 0124            SearchProviderName = Name,
 0125        };
 126
 0127        searchResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Id.ToString());
 128
 0129        return searchResult;
 130    }
 131
 132    /// <inheritdoc />
 133    public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
 134    {
 0135        var result = new MetadataResult<MusicArtist> { Item = new MusicArtist() };
 136
 0137        var musicBrainzId = info.GetMusicBrainzArtistId();
 138
 0139        if (string.IsNullOrWhiteSpace(musicBrainzId))
 140        {
 0141            var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
 142
 0143            var singleResult = searchResults.FirstOrDefault();
 144
 0145            if (singleResult is not null)
 146            {
 0147                musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist);
 0148                result.Item.Overview = singleResult.Overview;
 149
 0150                if (Plugin.Instance!.Configuration.ReplaceArtistName)
 151                {
 0152                    result.Item.Name = singleResult.Name;
 153                }
 154            }
 155        }
 156
 0157        if (!string.IsNullOrWhiteSpace(musicBrainzId))
 158        {
 0159            result.HasMetadata = true;
 0160            result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId);
 161        }
 162
 0163        return result;
 0164    }
 165
 166    /// <inheritdoc />
 167    public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
 168    {
 0169        throw new NotImplementedException();
 170    }
 171
 172    /// <inheritdoc />
 173    public void Dispose()
 174    {
 21175        Dispose(true);
 21176        GC.SuppressFinalize(this);
 21177    }
 178
 179    /// <summary>
 180    /// Dispose all resources.
 181    /// </summary>
 182    /// <param name="disposing">Whether to dispose.</param>
 183    protected virtual void Dispose(bool disposing)
 184    {
 21185        if (disposing)
 186        {
 21187            _musicBrainzQuery.Dispose();
 188        }
 21189    }
 190}