implement music identification

This commit is contained in:
Luke Pulverenti 2014-03-13 23:23:58 -04:00
parent 45a1113d8f
commit 87ebe39107
4 changed files with 209 additions and 51 deletions

View File

@ -3,6 +3,7 @@ using MediaBrowser.Common.IO;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -67,6 +68,18 @@ namespace MediaBrowser.Api
{ {
} }
[Route("/Items/RemoteSearch/MusicArtist", "POST")]
[Api(Description = "Gets external id infos for an item")]
public class GetMusicArtistRemoteSearchResults : RemoteSearchQuery<ArtistInfo>, IReturn<List<RemoteSearchResult>>
{
}
[Route("/Items/RemoteSearch/MusicAlbum", "POST")]
[Api(Description = "Gets external id infos for an item")]
public class GetMusicAlbumRemoteSearchResults : RemoteSearchQuery<AlbumInfo>, IReturn<List<RemoteSearchResult>>
{
}
[Route("/Items/RemoteSearch/Person", "POST")] [Route("/Items/RemoteSearch/Person", "POST")]
[Api(Description = "Gets external id infos for an item")] [Api(Description = "Gets external id infos for an item")]
public class GetPersonRemoteSearchResults : RemoteSearchQuery<PersonLookupInfo>, IReturn<List<RemoteSearchResult>> public class GetPersonRemoteSearchResults : RemoteSearchQuery<PersonLookupInfo>, IReturn<List<RemoteSearchResult>>
@ -167,6 +180,20 @@ namespace MediaBrowser.Api
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }
public object Post(GetMusicAlbumRemoteSearchResults request)
{
var result = _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(request, CancellationToken.None).Result;
return ToOptimizedResult(result);
}
public object Post(GetMusicArtistRemoteSearchResults request)
{
var result = _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(request, CancellationToken.None).Result;
return ToOptimizedResult(result);
}
public object Get(GetRemoteSearchImage request) public object Get(GetRemoteSearchImage request)
{ {
var result = GetRemoteImage(request).Result; var result = GetRemoteImage(request).Result;

View File

@ -96,6 +96,9 @@
<Name>MediaBrowser.Model</Name> <Name>MediaBrowser.Model</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Server\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -7,6 +7,7 @@ using MediaBrowser.Model.Providers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -31,9 +32,86 @@ namespace MediaBrowser.Providers.Music
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
{ {
var releaseId = searchInfo.GetReleaseId();
string url = null;
if (!string.IsNullOrEmpty(releaseId))
{
url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=reid:{0}", releaseId);
}
else
{
var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId();
if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
{
url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND arid:{1}",
WebUtility.UrlEncode(searchInfo.Name),
artistMusicBrainzId);
}
else
{
url = string.Format("http://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
WebUtility.UrlEncode(searchInfo.Name),
WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
}
}
if (!string.IsNullOrWhiteSpace(url))
{
var doc = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
return GetResultsFromResponse(doc);
}
return new List<RemoteSearchResult>(); return new List<RemoteSearchResult>();
} }
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(XmlDocument doc)
{
var ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("mb", "http://musicbrainz.org/ns/mmd-2.0#");
var list = new List<RemoteSearchResult>();
var nodes = doc.SelectNodes("//mb:release-list/mb:release", ns);
if (nodes != null)
{
foreach (var node in nodes.Cast<XmlNode>())
{
if (node.Attributes != null)
{
string name = null;
string mbzId = node.Attributes["id"].Value;
var nameNode = node.SelectSingleNode("//mb:title", ns);
if (nameNode != null)
{
name = nameNode.InnerText;
}
if (!string.IsNullOrWhiteSpace(mbzId) && !string.IsNullOrWhiteSpace(name))
{
var result = new RemoteSearchResult
{
Name = name
};
result.SetProviderId(MetadataProviders.MusicBrainzAlbum, mbzId);
list.Add(result);
}
}
}
}
return list;
}
public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo id, CancellationToken cancellationToken) public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo id, CancellationToken cancellationToken)
{ {
var releaseId = id.GetReleaseId(); var releaseId = id.GetReleaseId();
@ -146,7 +224,7 @@ namespace MediaBrowser.Providers.Music
{ {
result.ReleaseGroupId = releaseGroupIdNode.Value; result.ReleaseGroupId = releaseGroupIdNode.Value;
} }
return result; return result;
} }

View File

@ -19,71 +19,121 @@ namespace MediaBrowser.Providers.Music
{ {
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
{ {
var musicBrainzId = searchInfo.GetMusicBrainzArtistId();
if (!string.IsNullOrWhiteSpace(musicBrainzId))
{
var url = string.Format("http://www.musicbrainz.org/ws/2/artist/?query=arid:{0}", musicBrainzId);
var doc = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken)
.ConfigureAwait(false);
return GetResultsFromResponse(doc);
}
else
{
// They seem to throw bad request failures on any term with a slash
var nameToSearch = searchInfo.Name.Replace('/', ' ');
var url = String.Format("http://www.musicbrainz.org/ws/2/artist/?query=artist:\"{0}\"", UrlEncode(nameToSearch));
var doc = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
var results = GetResultsFromResponse(doc).ToList();
if (results.Count > 0)
{
return results;
}
if (HasDiacritics(searchInfo.Name))
{
// Try again using the search with accent characters url
url = String.Format("http://www.musicbrainz.org/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
doc = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
return GetResultsFromResponse(doc);
}
}
return new List<RemoteSearchResult>(); return new List<RemoteSearchResult>();
} }
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(XmlDocument doc)
{
var ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("mb", "http://musicbrainz.org/ns/mmd-2.0#");
var list = new List<RemoteSearchResult>();
var nodes = doc.SelectNodes("//mb:artist-list/mb:artist", ns);
if (nodes != null)
{
foreach (var node in nodes.Cast<XmlNode>())
{
if (node.Attributes != null)
{
string name = null;
string mbzId = node.Attributes["id"].Value;
var nameNode = node.SelectSingleNode("//mb:name", ns);
if (nameNode != null)
{
name = nameNode.InnerText;
}
if (!string.IsNullOrWhiteSpace(mbzId) && !string.IsNullOrWhiteSpace(name))
{
var result = new RemoteSearchResult
{
Name = name
};
result.SetProviderId(MetadataProviders.MusicBrainzArtist, mbzId);
list.Add(result);
}
}
}
}
return list;
}
public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo id, CancellationToken cancellationToken) public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo id, CancellationToken cancellationToken)
{ {
var result = new MetadataResult<MusicArtist>(); var result = new MetadataResult<MusicArtist>
{
Item = new MusicArtist()
};
var musicBrainzId = id.GetMusicBrainzArtistId() ?? await FindId(id, cancellationToken).ConfigureAwait(false); var musicBrainzId = id.GetMusicBrainzArtistId();
if (string.IsNullOrWhiteSpace(musicBrainzId))
{
var searchResults = await GetSearchResults(id, cancellationToken).ConfigureAwait(false);
var singleResult = searchResults.FirstOrDefault();
if (singleResult != null)
{
musicBrainzId = singleResult.GetProviderId(MetadataProviders.MusicBrainzArtist);
result.Item.Name = singleResult.Name;
}
}
if (!string.IsNullOrWhiteSpace(musicBrainzId)) if (!string.IsNullOrWhiteSpace(musicBrainzId))
{ {
cancellationToken.ThrowIfCancellationRequested();
result.Item = new MusicArtist();
result.HasMetadata = true; result.HasMetadata = true;
result.Item.SetProviderId(MetadataProviders.MusicBrainzArtist, musicBrainzId); result.Item.SetProviderId(MetadataProviders.MusicBrainzArtist, musicBrainzId);
} }
return result; return result;
} }
/// <summary>
/// Finds the id from music brainz.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.String}.</returns>
private async Task<string> FindId(ItemLookupInfo item, CancellationToken cancellationToken)
{
// They seem to throw bad request failures on any term with a slash
var nameToSearch = item.Name.Replace('/', ' ');
var url = String.Format("http://www.musicbrainz.org/ws/2/artist/?query=artist:\"{0}\"", UrlEncode(nameToSearch));
var doc = await MusicBrainzAlbumProvider.Current.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:artist-list/mb:artist/@id", ns);
if (node != null && node.Value != null)
{
return node.Value;
}
if (HasDiacritics(item.Name))
{
// Try again using the search with accent characters url
url = String.Format("http://www.musicbrainz.org/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
doc = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("mb", "http://musicbrainz.org/ns/mmd-2.0#");
node = doc.SelectSingleNode("//mb:artist-list/mb:artist/@id", ns);
if (node != null && node.Value != null)
{
return node.Value;
}
}
return null;
}
/// <summary> /// <summary>
/// Determines whether the specified text has diacritics. /// Determines whether the specified text has diacritics.