|
|
@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
private readonly IApplicationHost _appHost;
|
|
|
|
private readonly IApplicationHost _appHost;
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
|
|
|
|
|
|
|
public static string MusicBrainzBaseUrl = "http://musicbrainz.fercasas.com:5000";
|
|
|
|
public static string MusicBrainzBaseUrl = "https://www.musicbrainz.org";
|
|
|
|
|
|
|
|
|
|
|
|
public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger)
|
|
|
|
public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger)
|
|
|
|
{
|
|
|
|
{
|
|
|
@ -44,7 +44,7 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(releaseId))
|
|
|
|
if (!string.IsNullOrEmpty(releaseId))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
url = string.Format(MusicBrainzBaseUrl + "/ws/2/release/?query=reid:{0}", releaseId);
|
|
|
|
url = string.Format("/ws/2/release/?query=reid:{0}", releaseId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
@ -52,7 +52,7 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
|
|
|
|
if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
url = string.Format(MusicBrainzBaseUrl + "/ws/2/release/?query=\"{0}\" AND arid:{1}",
|
|
|
|
url = string.Format("/ws/2/release/?query=\"{0}\" AND arid:{1}",
|
|
|
|
WebUtility.UrlEncode(searchInfo.Name),
|
|
|
|
WebUtility.UrlEncode(searchInfo.Name),
|
|
|
|
artistMusicBrainzId);
|
|
|
|
artistMusicBrainzId);
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
{
|
|
|
|
{
|
|
|
|
isNameSearch = true;
|
|
|
|
isNameSearch = true;
|
|
|
|
|
|
|
|
|
|
|
|
url = string.Format(MusicBrainzBaseUrl + "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
|
|
|
|
url = string.Format("/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
|
|
|
|
WebUtility.UrlEncode(searchInfo.Name),
|
|
|
|
WebUtility.UrlEncode(searchInfo.Name),
|
|
|
|
WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
|
|
|
|
WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -199,57 +199,86 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<ReleaseResult> GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
|
|
|
|
private async Task<ReleaseResult> GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var url = string.Format(MusicBrainzBaseUrl + "/ws/2/release/?query=\"{0}\" AND arid:{1}",
|
|
|
|
var url = string.Format("/ws/2/release/?query=\"{0}\" AND arid:{1}",
|
|
|
|
WebUtility.UrlEncode(albumName),
|
|
|
|
WebUtility.UrlEncode(albumName),
|
|
|
|
artistId);
|
|
|
|
artistId);
|
|
|
|
|
|
|
|
|
|
|
|
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
|
|
|
|
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
return GetReleaseResult(doc);
|
|
|
|
return ReleaseResult.Parse(doc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<ReleaseResult> GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
|
|
|
|
private async Task<ReleaseResult> GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var url = string.Format(MusicBrainzBaseUrl + "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
|
|
|
|
var url = string.Format("/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
|
|
|
|
WebUtility.UrlEncode(albumName),
|
|
|
|
WebUtility.UrlEncode(albumName),
|
|
|
|
WebUtility.UrlEncode(artistName));
|
|
|
|
WebUtility.UrlEncode(artistName));
|
|
|
|
|
|
|
|
|
|
|
|
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
|
|
|
|
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
return GetReleaseResult(doc);
|
|
|
|
return ReleaseResult.Parse(doc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private ReleaseResult GetReleaseResult(XmlDocument doc)
|
|
|
|
private class ReleaseResult
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var ns = new XmlNamespaceManager(doc.NameTable);
|
|
|
|
public string ReleaseId;
|
|
|
|
ns.AddNamespace("mb", MusicBrainzBaseUrl + "/ns/mmd-2.0#");
|
|
|
|
public string ReleaseGroupId;
|
|
|
|
|
|
|
|
|
|
|
|
var result = new ReleaseResult
|
|
|
|
public static ReleaseResult Parse(XmlDocument doc)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
var docElem = doc.DocumentElement;
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
if (docElem == null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return new ReleaseResult();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var releaseIdNode = doc.SelectSingleNode("//mb:release-list/mb:release/@id", ns);
|
|
|
|
var releaseList = docElem.FirstChild;
|
|
|
|
|
|
|
|
if (releaseList == null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return new ReleaseResult();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (releaseIdNode != null)
|
|
|
|
var nodes = releaseList.ChildNodes;
|
|
|
|
{
|
|
|
|
string releaseId = null;
|
|
|
|
result.ReleaseId = releaseIdNode.Value;
|
|
|
|
string releaseGroupId = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var releaseGroupIdNode = doc.SelectSingleNode("//mb:release-list/mb:release/mb:release-group/@id", ns);
|
|
|
|
if (nodes != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
foreach (var node in nodes.Cast<XmlNode>())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (string.Equals(node.Name, "release", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
releaseId = node.Attributes["id"].Value;
|
|
|
|
|
|
|
|
releaseGroupId = GetReleaseGroupIdFromReleaseNode(node);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (releaseGroupIdNode != null)
|
|
|
|
return new ReleaseResult
|
|
|
|
{
|
|
|
|
{
|
|
|
|
result.ReleaseGroupId = releaseGroupIdNode.Value;
|
|
|
|
ReleaseId = releaseId,
|
|
|
|
|
|
|
|
ReleaseGroupId = releaseGroupId
|
|
|
|
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
private static string GetReleaseGroupIdFromReleaseNode(XmlNode node)
|
|
|
|
}
|
|
|
|
{
|
|
|
|
|
|
|
|
var subNodes = node.ChildNodes;
|
|
|
|
|
|
|
|
if (subNodes != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
foreach (var subNode in subNodes.Cast<XmlNode>())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (string.Equals(subNode.Name, "release-group", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return subNode.Attributes["id"].Value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class ReleaseResult
|
|
|
|
return null;
|
|
|
|
{
|
|
|
|
}
|
|
|
|
public string ReleaseId;
|
|
|
|
|
|
|
|
public string ReleaseGroupId;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
@ -260,7 +289,7 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
/// <returns>Task{System.String}.</returns>
|
|
|
|
/// <returns>Task{System.String}.</returns>
|
|
|
|
private async Task<string> GetReleaseGroupId(string releaseEntryId, CancellationToken cancellationToken)
|
|
|
|
private async Task<string> GetReleaseGroupId(string releaseEntryId, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var url = string.Format(MusicBrainzBaseUrl + "/ws/2/release-group/?query=reid:{0}", releaseEntryId);
|
|
|
|
var url = string.Format("/ws/2/release-group/?query=reid:{0}", releaseEntryId);
|
|
|
|
|
|
|
|
|
|
|
|
var doc = await GetMusicBrainzResponse(url, false, cancellationToken).ConfigureAwait(false);
|
|
|
|
var doc = await GetMusicBrainzResponse(url, false, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
@ -276,6 +305,49 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
private readonly SemaphoreSlim _musicBrainzResourcePool = new SemaphoreSlim(1, 1);
|
|
|
|
private readonly SemaphoreSlim _musicBrainzResourcePool = new SemaphoreSlim(1, 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private long _lastMbzUrlQueryTicks = 0;
|
|
|
|
|
|
|
|
private List<MbzUrl> _mbzUrls = null;
|
|
|
|
|
|
|
|
private MbzUrl _chosenUrl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<MbzUrl> GetMbzUrl()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (_mbzUrls == null || (DateTime.UtcNow.Ticks - _lastMbzUrlQueryTicks) > TimeSpan.FromHours(12).Ticks)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await RefreshMzbUrls().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var urls = _mbzUrls.ToList();
|
|
|
|
|
|
|
|
_chosenUrl = urls[new Random().Next(0, urls.Count - 1)];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return _chosenUrl;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private async Task RefreshMzbUrls()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_mbzUrls = new List<MbzUrl>
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
new MbzUrl
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
url = MusicBrainzBaseUrl,
|
|
|
|
|
|
|
|
throttleMs = 1000
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_mbzUrls = new List<MbzUrl>
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
new MbzUrl
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
url = MusicBrainzBaseUrl,
|
|
|
|
|
|
|
|
throttleMs = 1000
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the music brainz response.
|
|
|
|
/// Gets the music brainz response.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
@ -285,9 +357,15 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
/// <returns>Task{XmlDocument}.</returns>
|
|
|
|
/// <returns>Task{XmlDocument}.</returns>
|
|
|
|
internal async Task<XmlDocument> GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken)
|
|
|
|
internal async Task<XmlDocument> GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// MusicBrainz is extremely adamant about limiting to one request per second
|
|
|
|
var urlInfo = await GetMbzUrl().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (urlInfo.throttleMs > 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// MusicBrainz is extremely adamant about limiting to one request per second
|
|
|
|
|
|
|
|
await Task.Delay(urlInfo.throttleMs, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
|
|
|
url = urlInfo.url.TrimEnd('/') + url;
|
|
|
|
|
|
|
|
|
|
|
|
var doc = new XmlDocument();
|
|
|
|
var doc = new XmlDocument();
|
|
|
|
|
|
|
|
|
|
|
@ -319,5 +397,11 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
{
|
|
|
|
{
|
|
|
|
throw new NotImplementedException();
|
|
|
|
throw new NotImplementedException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal class MbzUrl
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
public string url { get; set; }
|
|
|
|
|
|
|
|
public int throttleMs { get; set; }
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|