< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Plugins.AudioDb.AudioDbArtistProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
Line coverage
4%
Covered lines: 6
Uncovered lines: 132
Coverable lines: 138
Total lines: 446
Line coverage: 4.3%
Branch coverage
0%
Covered branches: 0
Total branches: 76
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/6/2026 - 12:15:23 AM Line coverage: 8.5% (6/70) Branch coverage: 0% (0/36) Total lines: 2947/29/2026 - 12:15:53 AM Line coverage: 4.3% (6/138) Branch coverage: 0% (0/76) Total lines: 446 5/6/2026 - 12:15:23 AM Line coverage: 8.5% (6/70) Branch coverage: 0% (0/36) Total lines: 2947/29/2026 - 12:15:53 AM Line coverage: 4.3% (6/138) Branch coverage: 0% (0/76) Total lines: 446

Coverage delta

Coverage delta 5 -5

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Name()100%210%
get_Order()100%210%
GetSearchResults()0%110100%
FetchArtists()0%2040%
ToRemoteSearchResult(...)0%7280%
GetMetadata()0%620%
GetArtist()0%110100%
ProcessResult(...)0%1056320%
EnsureArtistInfo()0%2040%
DownloadArtistInfo()100%210%
EnsureArtistInfoByAudioDbId()0%2040%
DownloadArtistInfo()0%620%
GetArtistDataPath(...)100%210%
GetArtistDataPath(...)100%210%
GetArtistInfoPath(...)100%210%
GetImageResponse(...)100%210%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CA1034, CS1591, CA1002, SA1028, SA1300
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Globalization;
 8using System.IO;
 9using System.Linq;
 10using System.Net.Http;
 11using System.Net.Http.Json;
 12using System.Text.Json;
 13using System.Threading;
 14using System.Threading.Tasks;
 15using Jellyfin.Extensions.Json;
 16using MediaBrowser.Common.Configuration;
 17using MediaBrowser.Common.Extensions;
 18using MediaBrowser.Common.Net;
 19using MediaBrowser.Controller.Configuration;
 20using MediaBrowser.Controller.Entities.Audio;
 21using MediaBrowser.Controller.Providers;
 22using MediaBrowser.Model.Entities;
 23using MediaBrowser.Model.IO;
 24using MediaBrowser.Model.Providers;
 25using MediaBrowser.Providers.Music;
 26
 27namespace MediaBrowser.Providers.Plugins.AudioDb
 28{
 29    public class AudioDbArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IHasOrder
 30    {
 31        private const string ApiKey = "195003";
 32        public const string BaseUrl = "https://www.theaudiodb.com/api/v1/json/" + ApiKey;
 33
 34        private readonly IServerConfigurationManager _config;
 35        private readonly IFileSystem _fileSystem;
 36        private readonly IHttpClientFactory _httpClientFactory;
 2237        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 38
 39        public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory http
 40        {
 2241            _config = config;
 2242            _fileSystem = fileSystem;
 2243            _httpClientFactory = httpClientFactory;
 2244            Current = this;
 2245        }
 46
 47        public static AudioDbArtistProvider Current { get; private set; }
 48
 49        /// <inheritdoc />
 050        public string Name => "TheAudioDB";
 51
 52        /// <inheritdoc />
 53        // After musicbrainz
 054        public int Order => 1;
 55
 56        /// <inheritdoc />
 57        public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken can
 58        {
 59            // Prefer a known TheAudioDB artist id.
 060            var audioDbId = searchInfo.GetProviderId(MetadataProvider.AudioDbArtist);
 061            if (!string.IsNullOrWhiteSpace(audioDbId))
 62            {
 063                var artists = await FetchArtists(BaseUrl + "/artist.php?i=" + audioDbId, cancellationToken).ConfigureAwa
 064                return artists.Select(ToRemoteSearchResult);
 65            }
 66
 67            // Fall back to the MusicBrainz artist id, reusing the on-disk cache also used by GetMetadata.
 068            var musicBrainzId = searchInfo.GetMusicBrainzArtistId();
 069            if (!string.IsNullOrWhiteSpace(musicBrainzId))
 70            {
 071                await EnsureArtistInfo(musicBrainzId, cancellationToken).ConfigureAwait(false);
 72
 073                var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
 74
 075                FileStream jsonStream = AsyncFile.OpenRead(path);
 076                await using (jsonStream.ConfigureAwait(false))
 77                {
 078                    var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationTo
 79
 080                    if (obj is not null && obj.artists is not null)
 81                    {
 082                        return obj.artists.Select(ToRemoteSearchResult);
 83                    }
 84                }
 85
 086                return [];
 87            }
 88
 89            // Finally, search by name.
 090            if (!string.IsNullOrWhiteSpace(searchInfo.Name))
 91            {
 092                var artists = await FetchArtists(BaseUrl + "/search.php?s=" + Uri.EscapeDataString(searchInfo.Name), can
 093                return artists.Select(ToRemoteSearchResult);
 94            }
 95
 096            return [];
 097        }
 98
 99        private async Task<List<Artist>> FetchArtists(string url, CancellationToken cancellationToken)
 100        {
 0101            using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationTo
 0102            response.EnsureSuccessStatusCode();
 103
 0104            var obj = await response.Content.ReadFromJsonAsync<RootObject>(_jsonOptions, cancellationToken).ConfigureAwa
 105
 0106            return obj?.artists ?? [];
 0107        }
 108
 109        private RemoteSearchResult ToRemoteSearchResult(Artist artist)
 110        {
 0111            var result = new RemoteSearchResult
 0112            {
 0113                Name = artist.strArtist,
 0114                ImageUrl = artist.strArtistThumb,
 0115                SearchProviderName = Name,
 0116                Overview = (artist.strBiographyEN ?? string.Empty).StripHtml()
 0117            };
 118
 0119            if (!string.IsNullOrEmpty(artist.idArtist))
 120            {
 0121                result.SetProviderId(MetadataProvider.AudioDbArtist, artist.idArtist);
 122            }
 123
 0124            if (!string.IsNullOrEmpty(artist.strMusicBrainzID))
 125            {
 0126                result.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.strMusicBrainzID);
 127            }
 128
 0129            if (int.TryParse(artist.intFormedYear, NumberStyles.Integer, CultureInfo.InvariantCulture, out var formedYea
 130            {
 0131                result.ProductionYear = formedYear;
 132            }
 133
 0134            return result;
 135        }
 136
 137        /// <inheritdoc />
 138        public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
 139        {
 0140            var result = new MetadataResult<MusicArtist>();
 141
 0142            var artist = await GetArtist(
 0143                info.GetMusicBrainzArtistId(),
 0144                info.GetProviderId(MetadataProvider.AudioDbArtist),
 0145                cancellationToken).ConfigureAwait(false);
 146
 0147            if (artist is not null)
 148            {
 0149                result.Item = new MusicArtist();
 0150                result.HasMetadata = true;
 0151                ProcessResult(result.Item, artist, info.MetadataLanguage);
 152            }
 153
 0154            return result;
 0155        }
 156
 157        /// <summary>
 158        /// Resolves the cached AudioDB artist, preferring the MusicBrainz id and falling back to the AudioDB id.
 159        /// </summary>
 160        /// <param name="musicBrainzId">The MusicBrainz artist id, if known.</param>
 161        /// <param name="audioDbId">The TheAudioDB artist id, if known.</param>
 162        /// <param name="cancellationToken">The cancellation token.</param>
 163        /// <returns>The matching artist, or <c>null</c> if none could be resolved.</returns>
 164        internal async Task<Artist> GetArtist(string musicBrainzId, string audioDbId, CancellationToken cancellationToke
 165        {
 166            string path;
 0167            if (!string.IsNullOrWhiteSpace(musicBrainzId))
 168            {
 0169                await EnsureArtistInfo(musicBrainzId, cancellationToken).ConfigureAwait(false);
 0170                path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
 171            }
 0172            else if (!string.IsNullOrWhiteSpace(audioDbId))
 173            {
 0174                await EnsureArtistInfoByAudioDbId(audioDbId, cancellationToken).ConfigureAwait(false);
 0175                path = GetArtistInfoPath(_config.ApplicationPaths, audioDbId);
 176            }
 177            else
 178            {
 0179                return null;
 180            }
 181
 0182            FileStream jsonStream = AsyncFile.OpenRead(path);
 0183            await using (jsonStream.ConfigureAwait(false))
 184            {
 0185                var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken)
 186
 0187                if (obj is not null && obj.artists is not null && obj.artists.Count > 0)
 188                {
 0189                    return obj.artists[0];
 190                }
 191            }
 192
 0193            return null;
 0194        }
 195
 196        private void ProcessResult(MusicArtist item, Artist result, string preferredLanguage)
 197        {
 0198            if (!string.IsNullOrWhiteSpace(result.strWebsite))
 199            {
 0200                item.HomePageUrl = result.strWebsite;
 201            }
 202
 0203            var genres = new List<string>();
 0204            if (!string.IsNullOrWhiteSpace(result.strGenre))
 205            {
 0206                genres.Add(result.strGenre);
 207            }
 208
 0209            if (!string.IsNullOrWhiteSpace(result.strSubGenre))
 210            {
 0211                genres.Add(result.strSubGenre);
 212            }
 213
 0214            if (genres.Count > 0)
 215            {
 0216                item.Genres = genres.ToArray();
 217            }
 218
 0219            if (int.TryParse(result.intFormedYear, NumberStyles.Integer, CultureInfo.InvariantCulture, out var formedYea
 220            {
 0221                item.ProductionYear = formedYear;
 222            }
 223
 0224            if (!string.IsNullOrWhiteSpace(result.strCountry))
 225            {
 0226                item.ProductionLocations = new[] { result.strCountry };
 227            }
 228
 0229            item.SetProviderId(MetadataProvider.AudioDbArtist, result.idArtist);
 0230            item.SetProviderId(MetadataProvider.MusicBrainzArtist, result.strMusicBrainzID);
 231
 0232            string overview = null;
 233
 0234            if (string.Equals(preferredLanguage, "de", StringComparison.OrdinalIgnoreCase))
 235            {
 0236                overview = result.strBiographyDE;
 237            }
 0238            else if (string.Equals(preferredLanguage, "fr", StringComparison.OrdinalIgnoreCase))
 239            {
 0240                overview = result.strBiographyFR;
 241            }
 0242            else if (string.Equals(preferredLanguage, "nl", StringComparison.OrdinalIgnoreCase))
 243            {
 0244                overview = result.strBiographyNL;
 245            }
 0246            else if (string.Equals(preferredLanguage, "ru", StringComparison.OrdinalIgnoreCase))
 247            {
 0248                overview = result.strBiographyRU;
 249            }
 0250            else if (string.Equals(preferredLanguage, "it", StringComparison.OrdinalIgnoreCase))
 251            {
 0252                overview = result.strBiographyIT;
 253            }
 0254            else if ((preferredLanguage ?? string.Empty).StartsWith("pt", StringComparison.OrdinalIgnoreCase))
 255            {
 0256                overview = result.strBiographyPT;
 257            }
 258
 0259            if (string.IsNullOrWhiteSpace(overview))
 260            {
 0261                overview = string.IsNullOrWhiteSpace(result.strBiographyEN)
 0262                    ? result.strBiography
 0263                    : result.strBiographyEN;
 264            }
 265
 0266            item.Overview = (overview ?? string.Empty).StripHtml();
 0267        }
 268
 269        internal async Task EnsureArtistInfo(string musicBrainzId, CancellationToken cancellationToken)
 270        {
 0271            var xmlPath = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
 272
 0273            var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath);
 274
 0275            if (fileInfo.Exists
 0276                && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
 277            {
 0278                return;
 279            }
 280
 0281            await DownloadArtistInfo(musicBrainzId, cancellationToken).ConfigureAwait(false);
 0282        }
 283
 284        internal async Task DownloadArtistInfo(string musicBrainzId, CancellationToken cancellationToken)
 285        {
 0286            var url = BaseUrl + "/artist-mb.php?i=" + musicBrainzId;
 0287            await DownloadArtistInfo(url, GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId), cancellationToken)
 0288        }
 289
 290        internal async Task EnsureArtistInfoByAudioDbId(string audioDbId, CancellationToken cancellationToken)
 291        {
 0292            var xmlPath = GetArtistInfoPath(_config.ApplicationPaths, audioDbId);
 293
 0294            var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath);
 295
 0296            if (fileInfo.Exists
 0297                && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
 298            {
 0299                return;
 300            }
 301
 0302            var url = BaseUrl + "/artist.php?i=" + audioDbId;
 0303            await DownloadArtistInfo(url, xmlPath, cancellationToken).ConfigureAwait(false);
 0304        }
 305
 306        private async Task DownloadArtistInfo(string url, string path, CancellationToken cancellationToken)
 307        {
 0308            cancellationToken.ThrowIfCancellationRequested();
 309
 0310            using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationTo
 0311            response.EnsureSuccessStatusCode();
 0312            Directory.CreateDirectory(Path.GetDirectoryName(path));
 313
 0314            var fileStreamOptions = AsyncFile.WriteOptions;
 0315            fileStreamOptions.Mode = FileMode.Create;
 0316            var xmlFileStream = new FileStream(path, fileStreamOptions);
 0317            await using (xmlFileStream.ConfigureAwait(false))
 318            {
 0319                await response.Content.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
 320            }
 0321        }
 322
 323        /// <summary>
 324        /// Gets the artist data path.
 325        /// </summary>
 326        /// <param name="appPaths">The application paths.</param>
 327        /// <param name="musicBrainzArtistId">The music brainz artist identifier.</param>
 328        /// <returns>System.String.</returns>
 329        private static string GetArtistDataPath(IApplicationPaths appPaths, string musicBrainzArtistId)
 0330            => Path.Combine(GetArtistDataPath(appPaths), musicBrainzArtistId);
 331
 332        /// <summary>
 333        /// Gets the artist data path.
 334        /// </summary>
 335        /// <param name="appPaths">The application paths.</param>
 336        /// <returns>System.String.</returns>
 337        private static string GetArtistDataPath(IApplicationPaths appPaths)
 0338            => Path.Combine(appPaths.CachePath, "audiodb-artist");
 339
 340        internal static string GetArtistInfoPath(IApplicationPaths appPaths, string musicBrainzArtistId)
 341        {
 0342            var dataPath = GetArtistDataPath(appPaths, musicBrainzArtistId);
 343
 0344            return Path.Combine(dataPath, "artist.json");
 345        }
 346
 347        /// <inheritdoc />
 348        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
 349        {
 0350            throw new NotImplementedException();
 351        }
 352
 353        public class Artist
 354        {
 355            public string idArtist { get; set; }
 356
 357            public string strArtist { get; set; }
 358
 359            public string strArtistAlternate { get; set; }
 360
 361            public object idLabel { get; set; }
 362
 363            public string intFormedYear { get; set; }
 364
 365            public string intBornYear { get; set; }
 366
 367            public object intDiedYear { get; set; }
 368
 369            public object strDisbanded { get; set; }
 370
 371            public string strGenre { get; set; }
 372
 373            public string strSubGenre { get; set; }
 374
 375            public string strWebsite { get; set; }
 376
 377            public string strFacebook { get; set; }
 378
 379            public string strTwitter { get; set; }
 380
 381            public string strBiography { get; set; }
 382
 383            public string strBiographyEN { get; set; }
 384
 385            public string strBiographyDE { get; set; }
 386
 387            public string strBiographyFR { get; set; }
 388
 389            public string strBiographyCN { get; set; }
 390
 391            public string strBiographyIT { get; set; }
 392
 393            public string strBiographyJP { get; set; }
 394
 395            public string strBiographyRU { get; set; }
 396
 397            public string strBiographyES { get; set; }
 398
 399            public string strBiographyPT { get; set; }
 400
 401            public string strBiographySE { get; set; }
 402
 403            public string strBiographyNL { get; set; }
 404
 405            public string strBiographyHU { get; set; }
 406
 407            public string strBiographyNO { get; set; }
 408
 409            public string strBiographyIL { get; set; }
 410
 411            public string strBiographyPL { get; set; }
 412
 413            public string strGender { get; set; }
 414
 415            public string intMembers { get; set; }
 416
 417            public string strCountry { get; set; }
 418
 419            public string strCountryCode { get; set; }
 420
 421            public string strArtistThumb { get; set; }
 422
 423            public string strArtistLogo { get; set; }
 424
 425            public string strArtistFanart { get; set; }
 426
 427            public string strArtistFanart2 { get; set; }
 428
 429            public string strArtistFanart3 { get; set; }
 430
 431            public string strArtistBanner { get; set; }
 432
 433            public string strMusicBrainzID { get; set; }
 434
 435            public object strLastFMChart { get; set; }
 436
 437            public string strLocked { get; set; }
 438        }
 439
 440#pragma warning disable CA2227
 441        public class RootObject
 442        {
 443            public List<Artist> artists { get; set; }
 444        }
 445    }
 446}