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; namespace MediaBrowser.Providers.Plugins.MusicBrainz; /// /// Music album metadata provider for MusicBrainz. /// public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder, IDisposable { private readonly Query _musicBrainzQuery; /// /// Initializes a new instance of the class. /// public MusicBrainzAlbumProvider() { _musicBrainzQuery = new Query(); } /// public string Name => "MusicBrainz"; /// public int Order => 0; /// public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) { var releaseId = searchInfo.GetReleaseId(); var releaseGroupId = searchInfo.GetReleaseGroupId(); if (!string.IsNullOrEmpty(releaseId)) { var releaseResult = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Releases, cancellationToken).ConfigureAwait(false); return GetResultFromResponse(releaseResult).SingleItemAsEnumerable(); } if (!string.IsNullOrEmpty(releaseGroupId)) { var releaseGroupResult = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.ReleaseGroups, null, cancellationToken).ConfigureAwait(false); return GetResultsFromResponse(releaseGroupResult.Releases); } var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId(); if (!string.IsNullOrWhiteSpace(artistMusicBrainzId)) { var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{searchInfo.Name}\" AND arid:{artistMusicBrainzId}", null, null, false, cancellationToken) .ConfigureAwait(false); if (releaseSearchResults.Results.Count > 0) { return GetResultsFromResponse(releaseSearchResults.Results); } } else { // I'm sure there is a better way but for now it resolves search for 12" Mixes var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal); var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{queryName}\" AND artist:\"{searchInfo.GetAlbumArtist()}\"c", null, null, false, cancellationToken) .ConfigureAwait(false); if (releaseSearchResults.Results.Count > 0) { return GetResultsFromResponse(releaseSearchResults.Results); } } return Enumerable.Empty(); } private IEnumerable GetResultsFromResponse(IEnumerable>? releaseSearchResults) { if (releaseSearchResults is null) { yield break; } foreach (var result in releaseSearchResults) { yield return GetResultFromResponse(result.Item); } } private IEnumerable GetResultsFromResponse(IEnumerable? releaseSearchResults) { if (releaseSearchResults is null) { yield break; } foreach (var result in releaseSearchResults) { yield return GetResultFromResponse(result); } } private RemoteSearchResult GetResultFromResponse(IRelease releaseSearchResult) { var searchResult = new RemoteSearchResult { Name = releaseSearchResult.Title, ProductionYear = releaseSearchResult.Date?.Year, PremiereDate = releaseSearchResult.Date?.NearestDate }; if (releaseSearchResult.ArtistCredit?.Count > 0) { searchResult.AlbumArtist = new RemoteSearchResult { SearchProviderName = Name, Name = releaseSearchResult.ArtistCredit[0].Name }; if (releaseSearchResult.ArtistCredit[0].Artist?.Id is not null) { searchResult.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, releaseSearchResult.ArtistCredit[0].Artist!.Id.ToString()); } } searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString()); if (releaseSearchResult.ReleaseGroup?.Id is not null) { searchResult.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseSearchResult.ReleaseGroup.Id.ToString()); } return searchResult; } /// public async Task> GetMetadata(AlbumInfo info, CancellationToken cancellationToken) { // TODO: This sets essentially nothing. As-is, it's mostly useless. Make it actually pull metadata and use it. var releaseId = info.GetReleaseId(); var releaseGroupId = info.GetReleaseGroupId(); var result = new MetadataResult { Item = new MusicAlbum() }; // If there is a release group, but no release ID, try to match the release if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId)) { // TODO: Actually try to match the release. Simply taking the first result is stupid. var releaseGroup = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.ReleaseGroups, null, cancellationToken).ConfigureAwait(false); var release = releaseGroup.Releases?.Count > 0 ? releaseGroup.Releases[0] : null; releaseId = release?.Id.ToString(); result.HasMetadata = true; } // If there is no release ID, lookup a release with the info we have if (string.IsNullOrWhiteSpace(releaseId)) { var artistMusicBrainzId = info.GetMusicBrainzArtistId(); IRelease? releaseResult = null; if (!string.IsNullOrEmpty(artistMusicBrainzId)) { var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{info.Name}\" AND arid:{artistMusicBrainzId}", null, null, false, cancellationToken) .ConfigureAwait(false); releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null; } else if (!string.IsNullOrEmpty(info.GetAlbumArtist())) { var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{info.Name}\" AND artist:{info.GetAlbumArtist()}", null, null, false, cancellationToken) .ConfigureAwait(false); releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null; } if (releaseResult != null) { releaseId = releaseResult.Id.ToString(); if (releaseResult.ReleaseGroup?.Id is not null) { releaseGroupId = releaseResult.ReleaseGroup.Id.ToString(); } result.HasMetadata = true; result.Item.ProductionYear = releaseResult.Date?.Year; result.Item.Overview = releaseResult.Annotation; } } // If we have a release ID but not a release group ID, lookup the release group if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId)) { var release = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Releases, cancellationToken).ConfigureAwait(false); releaseGroupId = release.ReleaseGroup?.Id.ToString(); result.HasMetadata = true; } // If we have a release ID and a release group ID if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId)) { result.HasMetadata = true; } if (result.HasMetadata) { if (!string.IsNullOrEmpty(releaseId)) { result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId); } if (!string.IsNullOrEmpty(releaseGroupId)) { result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId); } } return result; } /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { throw new NotImplementedException(); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Dispose all resources. /// /// Whether to dispose. protected virtual void Dispose(bool disposing) { if (disposing) { _musicBrainzQuery.Dispose(); } } }