Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions MediaBrowser.Providers/Manager/MetadataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,11 @@ internal static void MergeBaseItemData(
target.OriginalTitle = source.OriginalTitle;
}

if (replaceData || string.IsNullOrEmpty(target.HomePageUrl))
{
target.HomePageUrl = source.HomePageUrl;
}

if (replaceData || string.IsNullOrEmpty(target.OriginalLanguage))
{
target.OriginalLanguage = source.OriginalLanguage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,24 @@
#pragma warning disable CS1591

using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;

namespace MediaBrowser.Providers.Plugins.AudioDb
{
public class AudioDbArtistImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;

public AudioDbArtistImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory)
public AudioDbArtistImageProvider(IHttpClientFactory httpClientFactory)
{
_config = config;
_httpClientFactory = httpClientFactory;
}

Expand All @@ -54,22 +46,14 @@ public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
/// <inheritdoc />
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
if (item.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out var id))
{
await AudioDbArtistProvider.Current.EnsureArtistInfo(id, cancellationToken).ConfigureAwait(false);

var path = AudioDbArtistProvider.GetArtistInfoPath(_config.ApplicationPaths, id);
item.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out var musicBrainzId);
item.TryGetProviderId(MetadataProvider.AudioDbArtist, out var audioDbId);

FileStream jsonStream = AsyncFile.OpenRead(path);
await using (jsonStream.ConfigureAwait(false))
{
var obj = await JsonSerializer.DeserializeAsync<AudioDbArtistProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
var artist = await AudioDbArtistProvider.Current.GetArtist(musicBrainzId, audioDbId, cancellationToken).ConfigureAwait(false);

if (obj is not null && obj.artists is not null && obj.artists.Count > 0)
{
return GetImages(obj.artists[0]);
}
}
if (artist is not null)
{
return GetImages(artist);
}

return [];
Expand Down
192 changes: 172 additions & 20 deletions MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -52,45 +54,176 @@ public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fil
public int Order => 1;

/// <inheritdoc />
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
=> Task.FromResult(Enumerable.Empty<RemoteSearchResult>());

/// <inheritdoc />
public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
{
var result = new MetadataResult<MusicArtist>();
var id = info.GetMusicBrainzArtistId();
// Prefer a known TheAudioDB artist id.
var audioDbId = searchInfo.GetProviderId(MetadataProvider.AudioDbArtist);
if (!string.IsNullOrWhiteSpace(audioDbId))
{
var artists = await FetchArtists(BaseUrl + "/artist.php?i=" + audioDbId, cancellationToken).ConfigureAwait(false);
return artists.Select(ToRemoteSearchResult);
}

if (!string.IsNullOrWhiteSpace(id))
// Fall back to the MusicBrainz artist id, reusing the on-disk cache also used by GetMetadata.
var musicBrainzId = searchInfo.GetMusicBrainzArtistId();
if (!string.IsNullOrWhiteSpace(musicBrainzId))
{
await EnsureArtistInfo(id, cancellationToken).ConfigureAwait(false);
await EnsureArtistInfo(musicBrainzId, cancellationToken).ConfigureAwait(false);

var path = GetArtistInfoPath(_config.ApplicationPaths, id);
var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);

FileStream jsonStream = AsyncFile.OpenRead(path);
await using (jsonStream.ConfigureAwait(false))
{
var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);

if (obj is not null && obj.artists is not null && obj.artists.Count > 0)
if (obj is not null && obj.artists is not null)
{
result.Item = new MusicArtist();
result.HasMetadata = true;
ProcessResult(result.Item, obj.artists[0], info.MetadataLanguage);
return obj.artists.Select(ToRemoteSearchResult);
}
}

return [];
}

// Finally, search by name.
if (!string.IsNullOrWhiteSpace(searchInfo.Name))
{
var artists = await FetchArtists(BaseUrl + "/search.php?s=" + Uri.EscapeDataString(searchInfo.Name), cancellationToken).ConfigureAwait(false);
return artists.Select(ToRemoteSearchResult);
}

return [];
}

private async Task<List<Artist>> FetchArtists(string url, CancellationToken cancellationToken)
{
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();

var obj = await response.Content.ReadFromJsonAsync<RootObject>(_jsonOptions, cancellationToken).ConfigureAwait(false);

return obj?.artists ?? [];
}

private RemoteSearchResult ToRemoteSearchResult(Artist artist)
{
var result = new RemoteSearchResult
{
Name = artist.strArtist,
ImageUrl = artist.strArtistThumb,
SearchProviderName = Name,
Overview = (artist.strBiographyEN ?? string.Empty).StripHtml()
};

if (!string.IsNullOrEmpty(artist.idArtist))
{
result.SetProviderId(MetadataProvider.AudioDbArtist, artist.idArtist);
}

if (!string.IsNullOrEmpty(artist.strMusicBrainzID))
{
result.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.strMusicBrainzID);
}

if (int.TryParse(artist.intFormedYear, NumberStyles.Integer, CultureInfo.InvariantCulture, out var formedYear))
{
result.ProductionYear = formedYear;
}

return result;
}

/// <inheritdoc />
public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<MusicArtist>();

var artist = await GetArtist(
info.GetMusicBrainzArtistId(),
info.GetProviderId(MetadataProvider.AudioDbArtist),
cancellationToken).ConfigureAwait(false);

if (artist is not null)
{
result.Item = new MusicArtist();
result.HasMetadata = true;
ProcessResult(result.Item, artist, info.MetadataLanguage);
}

return result;
}

/// <summary>
/// Resolves the cached AudioDB artist, preferring the MusicBrainz id and falling back to the AudioDB id.
/// </summary>
/// <param name="musicBrainzId">The MusicBrainz artist id, if known.</param>
/// <param name="audioDbId">The TheAudioDB artist id, if known.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The matching artist, or <c>null</c> if none could be resolved.</returns>
internal async Task<Artist> GetArtist(string musicBrainzId, string audioDbId, CancellationToken cancellationToken)
{
string path;
if (!string.IsNullOrWhiteSpace(musicBrainzId))
{
await EnsureArtistInfo(musicBrainzId, cancellationToken).ConfigureAwait(false);
path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
}
else if (!string.IsNullOrWhiteSpace(audioDbId))
{
await EnsureArtistInfoByAudioDbId(audioDbId, cancellationToken).ConfigureAwait(false);
path = GetArtistInfoPath(_config.ApplicationPaths, audioDbId);
}
else
{
return null;
}

FileStream jsonStream = AsyncFile.OpenRead(path);
await using (jsonStream.ConfigureAwait(false))
{
var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);

if (obj is not null && obj.artists is not null && obj.artists.Count > 0)
{
return obj.artists[0];
}
}

return null;
}

private void ProcessResult(MusicArtist item, Artist result, string preferredLanguage)
{
// item.HomePageUrl = result.strWebsite;
if (!string.IsNullOrWhiteSpace(result.strWebsite))
{
item.HomePageUrl = result.strWebsite;
}

var genres = new List<string>();
if (!string.IsNullOrWhiteSpace(result.strGenre))
{
genres.Add(result.strGenre);
}

if (!string.IsNullOrWhiteSpace(result.strSubGenre))
{
genres.Add(result.strSubGenre);
}

if (genres.Count > 0)
{
item.Genres = genres.ToArray();
}

if (int.TryParse(result.intFormedYear, NumberStyles.Integer, CultureInfo.InvariantCulture, out var formedYear))
{
item.ProductionYear = formedYear;
}

if (!string.IsNullOrEmpty(result.strGenre))
if (!string.IsNullOrWhiteSpace(result.strCountry))
{
item.Genres = new[] { result.strGenre };
item.ProductionLocations = new[] { result.strCountry };
}

item.SetProviderId(MetadataProvider.AudioDbArtist, result.idArtist);
Expand Down Expand Up @@ -150,13 +283,32 @@ internal async Task EnsureArtistInfo(string musicBrainzId, CancellationToken can

internal async Task DownloadArtistInfo(string musicBrainzId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

var url = BaseUrl + "/artist-mb.php?i=" + musicBrainzId;
await DownloadArtistInfo(url, GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId), cancellationToken).ConfigureAwait(false);
}

internal async Task EnsureArtistInfoByAudioDbId(string audioDbId, CancellationToken cancellationToken)
{
var xmlPath = GetArtistInfoPath(_config.ApplicationPaths, audioDbId);

var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath);

if (fileInfo.Exists
&& (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
{
return;
}

var url = BaseUrl + "/artist.php?i=" + audioDbId;
await DownloadArtistInfo(url, xmlPath, cancellationToken).ConfigureAwait(false);
}

private async Task DownloadArtistInfo(string url, string path, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
Directory.CreateDirectory(Path.GetDirectoryName(path));

var fileStreamOptions = AsyncFile.WriteOptions;
Expand Down
Loading
Loading