MusicBrainzAlbumProvider.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. using MediaBrowser.Common;
  2. using MediaBrowser.Common.Net;
  3. using MediaBrowser.Controller.Entities.Audio;
  4. using MediaBrowser.Controller.Providers;
  5. using MediaBrowser.Model.Entities;
  6. using MediaBrowser.Model.Providers;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Net;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using System.Xml;
  16. namespace MediaBrowser.Providers.Music
  17. {
  18. public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder
  19. {
  20. internal static MusicBrainzAlbumProvider Current;
  21. private readonly IHttpClient _httpClient;
  22. private readonly IApplicationHost _appHost;
  23. public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost)
  24. {
  25. _httpClient = httpClient;
  26. _appHost = appHost;
  27. Current = this;
  28. }
  29. public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
  30. {
  31. var releaseId = searchInfo.GetReleaseId();
  32. string url = null;
  33. if (!string.IsNullOrEmpty(releaseId))
  34. {
  35. url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=reid:{0}", releaseId);
  36. }
  37. else
  38. {
  39. var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId();
  40. if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
  41. {
  42. url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND arid:{1}",
  43. WebUtility.UrlEncode(searchInfo.Name),
  44. artistMusicBrainzId);
  45. }
  46. else
  47. {
  48. url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
  49. WebUtility.UrlEncode(searchInfo.Name),
  50. WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
  51. }
  52. }
  53. if (!string.IsNullOrWhiteSpace(url))
  54. {
  55. var doc = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
  56. return GetResultsFromResponse(doc);
  57. }
  58. return new List<RemoteSearchResult>();
  59. }
  60. private IEnumerable<RemoteSearchResult> GetResultsFromResponse(XmlDocument doc)
  61. {
  62. var ns = new XmlNamespaceManager(doc.NameTable);
  63. ns.AddNamespace("mb", "http://musicbrainz.org/ns/mmd-2.0#");
  64. var list = new List<RemoteSearchResult>();
  65. var nodes = doc.SelectNodes("//mb:release-list/mb:release", ns);
  66. if (nodes != null)
  67. {
  68. foreach (var node in nodes.Cast<XmlNode>())
  69. {
  70. if (node.Attributes != null)
  71. {
  72. string name = null;
  73. string mbzId = node.Attributes["id"].Value;
  74. var nameNode = node.SelectSingleNode("//mb:title", ns);
  75. if (nameNode != null)
  76. {
  77. name = nameNode.InnerText;
  78. }
  79. if (!string.IsNullOrWhiteSpace(mbzId) && !string.IsNullOrWhiteSpace(name))
  80. {
  81. var result = new RemoteSearchResult
  82. {
  83. Name = name
  84. };
  85. result.SetProviderId(MetadataProviders.MusicBrainzAlbum, mbzId);
  86. list.Add(result);
  87. }
  88. }
  89. }
  90. }
  91. return list;
  92. }
  93. public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo id, CancellationToken cancellationToken)
  94. {
  95. var releaseId = id.GetReleaseId();
  96. var releaseGroupId = id.GetReleaseGroupId();
  97. var result = new MetadataResult<MusicAlbum>
  98. {
  99. Item = new MusicAlbum()
  100. };
  101. if (string.IsNullOrEmpty(releaseId))
  102. {
  103. var artistMusicBrainzId = id.GetMusicBrainzArtistId();
  104. var releaseResult = await GetReleaseResult(artistMusicBrainzId, id.GetAlbumArtist(), id.Name, cancellationToken).ConfigureAwait(false);
  105. if (!string.IsNullOrEmpty(releaseResult.ReleaseId))
  106. {
  107. releaseId = releaseResult.ReleaseId;
  108. result.HasMetadata = true;
  109. }
  110. if (!string.IsNullOrEmpty(releaseResult.ReleaseGroupId))
  111. {
  112. releaseGroupId = releaseResult.ReleaseGroupId;
  113. result.HasMetadata = true;
  114. }
  115. }
  116. // If we have a release Id but not a release group Id...
  117. if (!string.IsNullOrEmpty(releaseId) && string.IsNullOrEmpty(releaseGroupId))
  118. {
  119. releaseGroupId = await GetReleaseGroupId(releaseId, cancellationToken).ConfigureAwait(false);
  120. result.HasMetadata = true;
  121. }
  122. if (result.HasMetadata)
  123. {
  124. if (!string.IsNullOrEmpty(releaseId))
  125. {
  126. result.Item.SetProviderId(MetadataProviders.MusicBrainzAlbum, releaseId);
  127. }
  128. if (!string.IsNullOrEmpty(releaseGroupId))
  129. {
  130. result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId);
  131. }
  132. }
  133. return result;
  134. }
  135. public string Name
  136. {
  137. get { return "MusicBrainz"; }
  138. }
  139. private Task<ReleaseResult> GetReleaseResult(string artistMusicBrainId, string artistName, string albumName, CancellationToken cancellationToken)
  140. {
  141. if (!string.IsNullOrEmpty(artistMusicBrainId))
  142. {
  143. return GetReleaseResult(albumName, artistMusicBrainId, cancellationToken);
  144. }
  145. return GetReleaseResultByArtistName(albumName, artistName, cancellationToken);
  146. }
  147. private async Task<ReleaseResult> GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
  148. {
  149. var url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND arid:{1}",
  150. WebUtility.UrlEncode(albumName),
  151. artistId);
  152. var doc = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
  153. return GetReleaseResult(doc);
  154. }
  155. private async Task<ReleaseResult> GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
  156. {
  157. var url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
  158. WebUtility.UrlEncode(albumName),
  159. WebUtility.UrlEncode(artistName));
  160. var doc = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
  161. return GetReleaseResult(doc);
  162. }
  163. private ReleaseResult GetReleaseResult(XmlDocument doc)
  164. {
  165. var ns = new XmlNamespaceManager(doc.NameTable);
  166. ns.AddNamespace("mb", "http://musicbrainz.org/ns/mmd-2.0#");
  167. var result = new ReleaseResult
  168. {
  169. };
  170. var releaseIdNode = doc.SelectSingleNode("//mb:release-list/mb:release/@id", ns);
  171. if (releaseIdNode != null)
  172. {
  173. result.ReleaseId = releaseIdNode.Value;
  174. }
  175. var releaseGroupIdNode = doc.SelectSingleNode("//mb:release-list/mb:release/mb:release-group/@id", ns);
  176. if (releaseGroupIdNode != null)
  177. {
  178. result.ReleaseGroupId = releaseGroupIdNode.Value;
  179. }
  180. return result;
  181. }
  182. private class ReleaseResult
  183. {
  184. public string ReleaseId;
  185. public string ReleaseGroupId;
  186. }
  187. /// <summary>
  188. /// Gets the release group id internal.
  189. /// </summary>
  190. /// <param name="releaseEntryId">The release entry id.</param>
  191. /// <param name="cancellationToken">The cancellation token.</param>
  192. /// <returns>Task{System.String}.</returns>
  193. private async Task<string> GetReleaseGroupId(string releaseEntryId, CancellationToken cancellationToken)
  194. {
  195. var url = string.Format("http://www.musicbrainz.org/ws/2/release-group/?query=reid:{0}", releaseEntryId);
  196. var doc = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
  197. var ns = new XmlNamespaceManager(doc.NameTable);
  198. ns.AddNamespace("mb", "http://musicbrainz.org/ns/mmd-2.0#");
  199. var node = doc.SelectSingleNode("//mb:release-group-list/mb:release-group/@id", ns);
  200. return node != null ? node.Value : null;
  201. }
  202. /// <summary>
  203. /// The _last music brainz request
  204. /// </summary>
  205. private DateTime _lastRequestDate = DateTime.MinValue;
  206. /// <summary>
  207. /// The _music brainz resource pool
  208. /// </summary>
  209. private readonly SemaphoreSlim _musicBrainzResourcePool = new SemaphoreSlim(1, 1);
  210. /// <summary>
  211. /// Gets the music brainz response.
  212. /// </summary>
  213. /// <param name="url">The URL.</param>
  214. /// <param name="cancellationToken">The cancellation token.</param>
  215. /// <returns>Task{XmlDocument}.</returns>
  216. internal async Task<XmlDocument> GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
  217. {
  218. await _musicBrainzResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
  219. try
  220. {
  221. var diff = 1500 - (DateTime.Now - _lastRequestDate).TotalMilliseconds;
  222. // MusicBrainz is extremely adamant about limiting to one request per second
  223. if (diff > 0)
  224. {
  225. await Task.Delay(Convert.ToInt32(diff), cancellationToken).ConfigureAwait(false);
  226. }
  227. _lastRequestDate = DateTime.Now;
  228. var doc = new XmlDocument();
  229. var userAgent = _appHost.Name + "/" + _appHost.ApplicationVersion;
  230. using (var xml = await _httpClient.Get(new HttpRequestOptions
  231. {
  232. Url = url,
  233. CancellationToken = cancellationToken,
  234. UserAgent = userAgent
  235. }).ConfigureAwait(false))
  236. {
  237. using (var oReader = new StreamReader(xml, Encoding.UTF8))
  238. {
  239. doc.Load(oReader);
  240. }
  241. }
  242. return doc;
  243. }
  244. finally
  245. {
  246. _lastRequestDate = DateTime.Now;
  247. _musicBrainzResourcePool.Release();
  248. }
  249. }
  250. public int Order
  251. {
  252. get { return 0; }
  253. }
  254. public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
  255. {
  256. throw new NotImplementedException();
  257. }
  258. }
  259. }