|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Net.Http;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using Jellyfin.Extensions;
|
|
|
|
using MediaBrowser.Controller.Entities.Audio;
|
|
|
|
using MediaBrowser.Controller.Providers;
|
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
using MediaBrowser.Model.Providers;
|
|
|
|
using MediaBrowser.Providers.Music;
|
|
|
|
using MetaBrainz.MusicBrainz;
|
|
|
|
using MetaBrainz.MusicBrainz.Interfaces.Entities;
|
|
|
|
using MetaBrainz.MusicBrainz.Interfaces.Searches;
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
|
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// MusicBrainz artist provider.
|
|
|
|
/// </summary>
|
|
|
|
public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable
|
|
|
|
{
|
|
|
|
private readonly ILogger<MusicBrainzArtistProvider> _logger;
|
|
|
|
private readonly Query _musicBrainzQuery;
|
|
|
|
private readonly string _musicBrainzDefaultUri = "https://musicbrainz.org";
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="MusicBrainzArtistProvider"/> class.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="logger">The logger.</param>
|
|
|
|
public MusicBrainzArtistProvider(ILogger<MusicBrainzArtistProvider> logger)
|
|
|
|
{
|
|
|
|
_logger = logger;
|
|
|
|
|
|
|
|
MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) =>
|
|
|
|
{
|
|
|
|
if (Uri.TryCreate(MusicBrainz.Plugin.Instance.Configuration.Server, UriKind.Absolute, out var server))
|
|
|
|
{
|
|
|
|
Query.DefaultServer = server.Host;
|
|
|
|
Query.DefaultPort = server.Port;
|
|
|
|
Query.DefaultUrlScheme = server.Scheme;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Fallback to official server
|
|
|
|
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
|
|
|
|
var defaultServer = new Uri(_musicBrainzDefaultUri);
|
|
|
|
Query.DefaultServer = defaultServer.Host;
|
|
|
|
Query.DefaultPort = defaultServer.Port;
|
|
|
|
Query.DefaultUrlScheme = defaultServer.Scheme;
|
|
|
|
}
|
|
|
|
|
|
|
|
Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit;
|
|
|
|
};
|
|
|
|
|
|
|
|
_musicBrainzQuery = new Query();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public string Name => "MusicBrainz";
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
var artistId = searchInfo.GetMusicBrainzArtistId();
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(artistId))
|
|
|
|
{
|
|
|
|
var artistResult = await _musicBrainzQuery.LookupArtistAsync(new Guid(artistId), Include.Aliases, null, null, cancellationToken).ConfigureAwait(false);
|
|
|
|
return GetResultFromResponse(artistResult).SingleItemAsEnumerable();
|
|
|
|
}
|
|
|
|
|
|
|
|
var artistSearchResults = await _musicBrainzQuery.FindArtistsAsync($"\"{searchInfo.Name}\"", null, null, false, cancellationToken)
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
if (artistSearchResults.Results.Count > 0)
|
|
|
|
{
|
|
|
|
return GetResultsFromResponse(artistSearchResults.Results);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (searchInfo.Name.HasDiacritics())
|
|
|
|
{
|
|
|
|
// Try again using the search with an accented characters query
|
|
|
|
var artistAccentsSearchResults = await _musicBrainzQuery.FindArtistsAsync($"artistaccent:\"{searchInfo.Name}\"", null, null, false, cancellationToken)
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
if (artistAccentsSearchResults.Results.Count > 0)
|
|
|
|
{
|
|
|
|
return GetResultsFromResponse(artistAccentsSearchResults.Results);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Enumerable.Empty<RemoteSearchResult>();
|
|
|
|
}
|
|
|
|
|
|
|
|
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(IEnumerable<ISearchResult<IArtist>>? releaseSearchResults)
|
|
|
|
{
|
|
|
|
if (releaseSearchResults is null)
|
|
|
|
{
|
|
|
|
yield break;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (var result in releaseSearchResults)
|
|
|
|
{
|
|
|
|
yield return GetResultFromResponse(result.Item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private RemoteSearchResult GetResultFromResponse(IArtist artist)
|
|
|
|
{
|
|
|
|
var searchResult = new RemoteSearchResult
|
|
|
|
{
|
|
|
|
Name = artist.Name,
|
|
|
|
ProductionYear = artist.LifeSpan?.Begin?.Year,
|
|
|
|
PremiereDate = artist.LifeSpan?.Begin?.NearestDate
|
|
|
|
};
|
|
|
|
|
|
|
|
searchResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Id.ToString());
|
|
|
|
|
|
|
|
return searchResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
var result = new MetadataResult<MusicArtist> { Item = new MusicArtist() };
|
|
|
|
|
|
|
|
var musicBrainzId = info.GetMusicBrainzArtistId();
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(musicBrainzId))
|
|
|
|
{
|
|
|
|
var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
var singleResult = searchResults.FirstOrDefault();
|
|
|
|
|
|
|
|
if (singleResult is not null)
|
|
|
|
{
|
|
|
|
musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist);
|
|
|
|
result.Item.Overview = singleResult.Overview;
|
|
|
|
|
|
|
|
if (Plugin.Instance!.Configuration.ReplaceArtistName)
|
|
|
|
{
|
|
|
|
result.Item.Name = singleResult.Name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(musicBrainzId))
|
|
|
|
{
|
|
|
|
result.HasMetadata = true;
|
|
|
|
result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
throw new NotImplementedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public void Dispose()
|
|
|
|
{
|
|
|
|
Dispose(true);
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Dispose all resources.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="disposing">Whether to dispose.</param>
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
{
|
|
|
|
if (disposing)
|
|
|
|
{
|
|
|
|
_musicBrainzQuery.Dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|