using MediaBrowser.Common; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using System; using System.IO; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; namespace MediaBrowser.Providers.Music { public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder { internal static MusicBrainzAlbumProvider Current; private readonly IHttpClient _httpClient; private readonly IApplicationHost _appHost; public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost) { _httpClient = httpClient; _appHost = appHost; Current = this; } public async Task> GetMetadata(ItemId id, CancellationToken cancellationToken) { var albumId = (AlbumId)id; var releaseId = albumId.GetProviderId(MetadataProviders.Musicbrainz); var releaseGroupId = albumId.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); var result = new MetadataResult(); if (string.IsNullOrEmpty(releaseId)) { string artistMusicBrainzId; albumId.ArtistProviderIds.TryGetValue(MetadataProviders.Musicbrainz.ToString(), out artistMusicBrainzId); var releaseResult = await GetReleaseResult(artistMusicBrainzId, albumId.AlbumArtist, albumId.Name, cancellationToken).ConfigureAwait(false); result.Item = new MusicAlbum(); if (!string.IsNullOrEmpty(releaseResult.ReleaseId)) { releaseId = releaseResult.ReleaseId; result.HasMetadata = true; result.Item.SetProviderId(MetadataProviders.Musicbrainz, releaseId); } if (!string.IsNullOrEmpty(releaseResult.ReleaseGroupId)) { releaseGroupId = releaseResult.ReleaseGroupId; result.HasMetadata = true; result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId); } } // If we have a release Id but not a release group Id... if (!string.IsNullOrEmpty(releaseId) && string.IsNullOrEmpty(releaseGroupId)) { releaseGroupId = await GetReleaseGroupId(releaseId, cancellationToken).ConfigureAwait(false); result.HasMetadata = true; result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId); } return result; } public string Name { get { return "MusicBrainz"; } } private Task GetReleaseResult(string artistMusicBrainId, string artistName, string albumName, CancellationToken cancellationToken) { if (!string.IsNullOrEmpty(artistMusicBrainId)) { return GetReleaseResult(albumName, artistMusicBrainId, cancellationToken); } return GetReleaseResultByArtistName(albumName, artistName, cancellationToken); } private async Task GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken) { var url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND arid:{1}", WebUtility.UrlEncode(albumName), artistId); var doc = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); return GetReleaseResult(doc); } private async Task GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken) { var url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", WebUtility.UrlEncode(albumName), WebUtility.UrlEncode(artistName)); var doc = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); return GetReleaseResult(doc); } private ReleaseResult GetReleaseResult(XmlDocument doc) { var ns = new XmlNamespaceManager(doc.NameTable); ns.AddNamespace("mb", "http://musicbrainz.org/ns/mmd-2.0#"); var result = new ReleaseResult { }; var releaseIdNode = doc.SelectSingleNode("//mb:release-list/mb:release/@id", ns); if (releaseIdNode != null) { result.ReleaseId = releaseIdNode.Value; } var releaseGroupIdNode = doc.SelectSingleNode("//mb:release-list/mb:release/mb:release-group/@id", ns); if (releaseGroupIdNode != null) { result.ReleaseGroupId = releaseGroupIdNode.Value; } return result; } private class ReleaseResult { public string ReleaseId; public string ReleaseGroupId; } /// /// Gets the release group id internal. /// /// The release entry id. /// The cancellation token. /// Task{System.String}. private async Task GetReleaseGroupId(string releaseEntryId, CancellationToken cancellationToken) { var url = string.Format("http://www.musicbrainz.org/ws/2/release-group/?query=reid:{0}", releaseEntryId); var doc = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); var ns = new XmlNamespaceManager(doc.NameTable); ns.AddNamespace("mb", "http://musicbrainz.org/ns/mmd-2.0#"); var node = doc.SelectSingleNode("//mb:release-group-list/mb:release-group/@id", ns); return node != null ? node.Value : null; } /// /// The _last music brainz request /// private DateTime _lastRequestDate = DateTime.MinValue; /// /// The _music brainz resource pool /// private readonly SemaphoreSlim _musicBrainzResourcePool = new SemaphoreSlim(1, 1); /// /// Gets the music brainz response. /// /// The URL. /// The cancellation token. /// Task{XmlDocument}. internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) { await _musicBrainzResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { var diff = 1500 - (DateTime.Now - _lastRequestDate).TotalMilliseconds; // MusicBrainz is extremely adamant about limiting to one request per second if (diff > 0) { await Task.Delay(Convert.ToInt32(diff), cancellationToken).ConfigureAwait(false); } _lastRequestDate = DateTime.Now; var doc = new XmlDocument(); var userAgent = _appHost.Name + "/" + _appHost.ApplicationVersion; using (var xml = await _httpClient.Get(new HttpRequestOptions { Url = url, CancellationToken = cancellationToken, UserAgent = userAgent }).ConfigureAwait(false)) { using (var oReader = new StreamReader(xml, Encoding.UTF8)) { doc.Load(oReader); } } return doc; } finally { _lastRequestDate = DateTime.Now; _musicBrainzResourcePool.Release(); } } public int Order { get { return 0; } } } }