< 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
36%
Covered lines: 19
Uncovered lines: 33
Coverable lines: 52
Total lines: 304
Line coverage: 36.5%
Branch coverage
13%
Covered branches: 3
Total branches: 22
Branch coverage: 13.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Name()100%210%
get_Order()100%210%
ReloadConfig(...)50%2.23261.53%
GetReleaseResult(...)0%342180%
GetImageResponse(...)100%210%
Dispose()100%11100%
Dispose(...)100%22100%

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.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/// Music album metadata provider for MusicBrainz.
 24/// </summary>
 25public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder, IDisposable
 26{
 27    private readonly ILogger<MusicBrainzAlbumProvider> _logger;
 28    private Query _musicBrainzQuery;
 29
 30    /// <summary>
 31    /// Initializes a new instance of the <see cref="MusicBrainzAlbumProvider"/> class.
 32    /// </summary>
 33    /// <param name="logger">The logger.</param>
 34    public MusicBrainzAlbumProvider(ILogger<MusicBrainzAlbumProvider> logger)
 35    {
 2236        _logger = logger;
 2237        _musicBrainzQuery = new Query();
 2238        ReloadConfig(null, MusicBrainz.Plugin.Instance!.Configuration);
 2239        MusicBrainz.Plugin.Instance!.ConfigurationChanged += ReloadConfig;
 2240    }
 41
 42    /// <inheritdoc />
 043    public string Name => "MusicBrainz";
 44
 45    /// <inheritdoc />
 046    public int Order => 0;
 47
 48    private void ReloadConfig(object? sender, BasePluginConfiguration e)
 49    {
 2250        var configuration = (PluginConfiguration)e;
 2251        if (Uri.TryCreate(configuration.Server, UriKind.Absolute, out var server))
 52        {
 2253            Query.DefaultServer = server.DnsSafeHost;
 2254            Query.DefaultPort = server.Port;
 2255            Query.DefaultUrlScheme = server.Scheme;
 56        }
 57        else
 58        {
 59            // Fallback to official server
 060            _logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
 061            var defaultServer = new Uri(PluginConfiguration.DefaultServer);
 062            Query.DefaultServer = defaultServer.Host;
 063            Query.DefaultPort = defaultServer.Port;
 064            Query.DefaultUrlScheme = defaultServer.Scheme;
 65        }
 66
 2267        Query.DelayBetweenRequests = configuration.RateLimit;
 2268        _musicBrainzQuery = new Query();
 2269    }
 70
 71    /// <inheritdoc />
 72    public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancella
 73    {
 74        var releaseId = searchInfo.GetReleaseId();
 75        var releaseGroupId = searchInfo.GetReleaseGroupId();
 76
 77        if (!string.IsNullOrEmpty(releaseId))
 78        {
 79            var releaseResult = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Artists | Includ
 80            return GetReleaseResult(releaseResult).SingleItemAsEnumerable();
 81        }
 82
 83        if (!string.IsNullOrEmpty(releaseGroupId))
 84        {
 85            var releaseGroupResult = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.R
 86            return GetReleaseGroupResult(releaseGroupResult.Releases);
 87        }
 88
 89        var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId();
 90
 91        if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
 92        {
 93            var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{searchInfo.Name}\" AND arid:{artis
 94                .ConfigureAwait(false);
 95
 96            if (releaseSearchResults.Results.Count > 0)
 97            {
 98                return GetReleaseSearchResult(releaseSearchResults.Results);
 99            }
 100        }
 101        else
 102        {
 103            // I'm sure there is a better way but for now it resolves search for 12" Mixes
 104            var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal);
 105
 106            var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{queryName}\" AND artist:\"{searchI
 107                .ConfigureAwait(false);
 108
 109            if (releaseSearchResults.Results.Count > 0)
 110            {
 111                return GetReleaseSearchResult(releaseSearchResults.Results);
 112            }
 113        }
 114
 115        return Enumerable.Empty<RemoteSearchResult>();
 116    }
 117
 118    private IEnumerable<RemoteSearchResult> GetReleaseSearchResult(IEnumerable<ISearchResult<IRelease>>? releaseSearchRe
 119    {
 120        if (releaseSearchResults is null)
 121        {
 122            yield break;
 123        }
 124
 125        foreach (var result in releaseSearchResults)
 126        {
 127            yield return GetReleaseResult(result.Item);
 128        }
 129    }
 130
 131    private IEnumerable<RemoteSearchResult> GetReleaseGroupResult(IEnumerable<IRelease>? releaseSearchResults)
 132    {
 133        if (releaseSearchResults is null)
 134        {
 135            yield break;
 136        }
 137
 138        foreach (var result in releaseSearchResults)
 139        {
 140            // Fetch full release info, otherwise artists are missing
 141            var fullResult = _musicBrainzQuery.LookupRelease(result.Id, Include.Artists | Include.ReleaseGroups);
 142            yield return GetReleaseResult(fullResult);
 143        }
 144    }
 145
 146    private RemoteSearchResult GetReleaseResult(IRelease releaseSearchResult)
 147    {
 0148        var searchResult = new RemoteSearchResult
 0149        {
 0150            Name = releaseSearchResult.Title,
 0151            ProductionYear = releaseSearchResult.Date?.Year,
 0152            PremiereDate = releaseSearchResult.Date?.NearestDate,
 0153            SearchProviderName = Name
 0154        };
 155
 156        // Add artists and use first as album artist
 0157        var artists = releaseSearchResult.ArtistCredit;
 0158        if (artists is not null && artists.Count > 0)
 159        {
 0160            var artistResults = new RemoteSearchResult[artists.Count];
 0161            for (int i = 0; i < artists.Count; i++)
 162            {
 0163                var artist = artists[i];
 0164                var artistResult = new RemoteSearchResult
 0165                {
 0166                    Name = artist.Name
 0167                };
 168
 0169                if (artist.Artist?.Id is not null)
 170                {
 0171                    artistResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Artist!.Id.ToString());
 172                }
 173
 0174                artistResults[i] = artistResult;
 175            }
 176
 0177            searchResult.AlbumArtist = artistResults[0];
 0178            searchResult.Artists = artistResults;
 179        }
 180
 0181        searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString());
 182
 0183        if (releaseSearchResult.ReleaseGroup?.Id is not null)
 184        {
 0185            searchResult.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseSearchResult.ReleaseGroup.Id.ToS
 186        }
 187
 0188        return searchResult;
 189    }
 190
 191    /// <inheritdoc />
 192    public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo info, CancellationToken cancellationToken)
 193    {
 194        // TODO: This sets essentially nothing. As-is, it's mostly useless. Make it actually pull metadata and use it.
 195        var releaseId = info.GetReleaseId();
 196        var releaseGroupId = info.GetReleaseGroupId();
 197
 198        var result = new MetadataResult<MusicAlbum>
 199        {
 200            Item = new MusicAlbum()
 201        };
 202
 203        // If there is a release group, but no release ID, try to match the release
 204        if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId))
 205        {
 206            // TODO: Actually try to match the release. Simply taking the first result is stupid.
 207            var releaseGroup = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.None, n
 208            var release = releaseGroup.Releases?.Count > 0 ? releaseGroup.Releases[0] : null;
 209            if (release is not null)
 210            {
 211                releaseId = release.Id.ToString();
 212                result.HasMetadata = true;
 213            }
 214        }
 215
 216        // If there is no release ID, lookup a release with the info we have
 217        if (string.IsNullOrWhiteSpace(releaseId))
 218        {
 219            var artistMusicBrainzId = info.GetMusicBrainzArtistId();
 220            IRelease? releaseResult = null;
 221
 222            if (!string.IsNullOrEmpty(artistMusicBrainzId))
 223            {
 224                var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{info.Name}\" AND arid:{artistM
 225                    .ConfigureAwait(false);
 226                releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null;
 227            }
 228            else if (!string.IsNullOrEmpty(info.GetAlbumArtist()))
 229            {
 230                var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{info.Name}\" AND artist:{info.
 231                    .ConfigureAwait(false);
 232                releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null;
 233            }
 234
 235            if (releaseResult is not null)
 236            {
 237                releaseId = releaseResult.Id.ToString();
 238
 239                if (releaseResult.ReleaseGroup?.Id is not null)
 240                {
 241                    releaseGroupId = releaseResult.ReleaseGroup.Id.ToString();
 242                }
 243
 244                result.HasMetadata = true;
 245                result.Item.ProductionYear = releaseResult.Date?.Year;
 246                result.Item.Overview = releaseResult.Annotation;
 247            }
 248        }
 249
 250        // If we have a release ID but not a release group ID, lookup the release group
 251        if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId))
 252        {
 253            var release = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.ReleaseGroups, cancell
 254            releaseGroupId = release.ReleaseGroup?.Id.ToString();
 255            result.HasMetadata = true;
 256        }
 257
 258        // If we have a release ID and a release group ID
 259        if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId))
 260        {
 261            result.HasMetadata = true;
 262        }
 263
 264        if (result.HasMetadata)
 265        {
 266            if (!string.IsNullOrEmpty(releaseId))
 267            {
 268                result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId);
 269            }
 270
 271            if (!string.IsNullOrEmpty(releaseGroupId))
 272            {
 273                result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId);
 274            }
 275        }
 276
 277        return result;
 278    }
 279
 280    /// <inheritdoc />
 281    public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
 282    {
 0283        throw new NotImplementedException();
 284    }
 285
 286    /// <inheritdoc />
 287    public void Dispose()
 288    {
 22289        Dispose(true);
 22290        GC.SuppressFinalize(this);
 22291    }
 292
 293    /// <summary>
 294    /// Dispose all resources.
 295    /// </summary>
 296    /// <param name="disposing">Whether to dispose.</param>
 297    protected virtual void Dispose(bool disposing)
 298    {
 22299        if (disposing)
 300        {
 22301            _musicBrainzQuery.Dispose();
 302        }
 22303    }
 304}