MusicBrainzArtistProvider.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Text;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using System.Xml;
  10. using MediaBrowser.Common.Net;
  11. using MediaBrowser.Controller.Entities.Audio;
  12. using MediaBrowser.Controller.Extensions;
  13. using MediaBrowser.Controller.Providers;
  14. using MediaBrowser.Model.Entities;
  15. using MediaBrowser.Model.Providers;
  16. namespace MediaBrowser.Providers.Music
  17. {
  18. public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>
  19. {
  20. public MusicBrainzArtistProvider()
  21. {
  22. }
  23. public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
  24. {
  25. var musicBrainzId = searchInfo.GetMusicBrainzArtistId();
  26. if (!string.IsNullOrWhiteSpace(musicBrainzId))
  27. {
  28. var url = string.Format("/ws/2/artist/?query=arid:{0}", musicBrainzId);
  29. using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
  30. {
  31. using (var stream = response.Content)
  32. {
  33. return GetResultsFromResponse(stream);
  34. }
  35. }
  36. }
  37. else
  38. {
  39. // They seem to throw bad request failures on any term with a slash
  40. var nameToSearch = searchInfo.Name.Replace('/', ' ');
  41. var url = string.Format("/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch));
  42. using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
  43. {
  44. using (var stream = response.Content)
  45. {
  46. var results = GetResultsFromResponse(stream).ToList();
  47. if (results.Count > 0)
  48. {
  49. return results;
  50. }
  51. }
  52. }
  53. if (HasDiacritics(searchInfo.Name))
  54. {
  55. // Try again using the search with accent characters url
  56. url = string.Format("/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
  57. using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
  58. {
  59. using (var stream = response.Content)
  60. {
  61. return GetResultsFromResponse(stream);
  62. }
  63. }
  64. }
  65. }
  66. return Enumerable.Empty<RemoteSearchResult>();
  67. }
  68. private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
  69. {
  70. using (var oReader = new StreamReader(stream, Encoding.UTF8))
  71. {
  72. var settings = new XmlReaderSettings()
  73. {
  74. ValidationType = ValidationType.None,
  75. CheckCharacters = false,
  76. IgnoreProcessingInstructions = true,
  77. IgnoreComments = true
  78. };
  79. using (var reader = XmlReader.Create(oReader, settings))
  80. {
  81. reader.MoveToContent();
  82. reader.Read();
  83. // Loop through each element
  84. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  85. {
  86. if (reader.NodeType == XmlNodeType.Element)
  87. {
  88. switch (reader.Name)
  89. {
  90. case "artist-list":
  91. {
  92. if (reader.IsEmptyElement)
  93. {
  94. reader.Read();
  95. continue;
  96. }
  97. using (var subReader = reader.ReadSubtree())
  98. {
  99. return ParseArtistList(subReader).ToList();
  100. }
  101. }
  102. default:
  103. {
  104. reader.Skip();
  105. break;
  106. }
  107. }
  108. }
  109. else
  110. {
  111. reader.Read();
  112. }
  113. }
  114. return Enumerable.Empty<RemoteSearchResult>();
  115. }
  116. }
  117. }
  118. private IEnumerable<RemoteSearchResult> ParseArtistList(XmlReader reader)
  119. {
  120. reader.MoveToContent();
  121. reader.Read();
  122. // Loop through each element
  123. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  124. {
  125. if (reader.NodeType == XmlNodeType.Element)
  126. {
  127. switch (reader.Name)
  128. {
  129. case "artist":
  130. {
  131. if (reader.IsEmptyElement)
  132. {
  133. reader.Read();
  134. continue;
  135. }
  136. var mbzId = reader.GetAttribute("id");
  137. using (var subReader = reader.ReadSubtree())
  138. {
  139. var artist = ParseArtist(subReader, mbzId);
  140. if (artist != null)
  141. {
  142. yield return artist;
  143. }
  144. }
  145. break;
  146. }
  147. default:
  148. {
  149. reader.Skip();
  150. break;
  151. }
  152. }
  153. }
  154. else
  155. {
  156. reader.Read();
  157. }
  158. }
  159. }
  160. private RemoteSearchResult ParseArtist(XmlReader reader, string artistId)
  161. {
  162. var result = new RemoteSearchResult();
  163. reader.MoveToContent();
  164. reader.Read();
  165. // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
  166. // Loop through each element
  167. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  168. {
  169. if (reader.NodeType == XmlNodeType.Element)
  170. {
  171. switch (reader.Name)
  172. {
  173. case "name":
  174. {
  175. result.Name = reader.ReadElementContentAsString();
  176. break;
  177. }
  178. case "annotation":
  179. {
  180. result.Overview = reader.ReadElementContentAsString();
  181. break;
  182. }
  183. default:
  184. {
  185. // there is sort-name if ever needed
  186. reader.Skip();
  187. break;
  188. }
  189. }
  190. }
  191. else
  192. {
  193. reader.Read();
  194. }
  195. }
  196. result.SetProviderId(MetadataProviders.MusicBrainzArtist, artistId);
  197. if (string.IsNullOrWhiteSpace(artistId) || string.IsNullOrWhiteSpace(result.Name))
  198. {
  199. return null;
  200. }
  201. return result;
  202. }
  203. public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo id, CancellationToken cancellationToken)
  204. {
  205. var result = new MetadataResult<MusicArtist>
  206. {
  207. Item = new MusicArtist()
  208. };
  209. var musicBrainzId = id.GetMusicBrainzArtistId();
  210. if (string.IsNullOrWhiteSpace(musicBrainzId))
  211. {
  212. var searchResults = await GetSearchResults(id, cancellationToken).ConfigureAwait(false);
  213. var singleResult = searchResults.FirstOrDefault();
  214. if (singleResult != null)
  215. {
  216. musicBrainzId = singleResult.GetProviderId(MetadataProviders.MusicBrainzArtist);
  217. //result.Item.Name = singleResult.Name;
  218. result.Item.Overview = singleResult.Overview;
  219. }
  220. }
  221. if (!string.IsNullOrWhiteSpace(musicBrainzId))
  222. {
  223. result.HasMetadata = true;
  224. result.Item.SetProviderId(MetadataProviders.MusicBrainzArtist, musicBrainzId);
  225. }
  226. return result;
  227. }
  228. /// <summary>
  229. /// Determines whether the specified text has diacritics.
  230. /// </summary>
  231. /// <param name="text">The text.</param>
  232. /// <returns><c>true</c> if the specified text has diacritics; otherwise, <c>false</c>.</returns>
  233. private bool HasDiacritics(string text)
  234. {
  235. return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
  236. }
  237. /// <summary>
  238. /// Encodes an URL.
  239. /// </summary>
  240. /// <param name="name">The name.</param>
  241. /// <returns>System.String.</returns>
  242. private static string UrlEncode(string name)
  243. {
  244. return WebUtility.UrlEncode(name);
  245. }
  246. public string Name => "MusicBrainz";
  247. public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
  248. {
  249. throw new NotImplementedException();
  250. }
  251. }
  252. }