diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index 6c4fa71e2..3c6485fcc 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -18,6 +18,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; using CommonIO; +using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Music { @@ -27,12 +28,14 @@ namespace MediaBrowser.Providers.Music private readonly IServerConfigurationManager _config; private readonly IHttpClient _httpClient; private readonly IFileSystem _fileSystem; + private readonly IJsonSerializer _jsonSerializer; - public FanartAlbumProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem) + public FanartAlbumProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IJsonSerializer jsonSerializer) { _config = config; _httpClient = httpClient; _fileSystem = fileSystem; + _jsonSerializer = jsonSerializer; } public string Name @@ -69,9 +72,9 @@ namespace MediaBrowser.Providers.Music if (!string.IsNullOrEmpty(artistMusicBrainzId)) { - await FanartArtistProvider.Current.EnsureArtistXml(artistMusicBrainzId, cancellationToken).ConfigureAwait(false); + await FanartArtistProvider.Current.EnsureArtistJson(artistMusicBrainzId, cancellationToken).ConfigureAwait(false); - var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId); + var artistJsonPath = FanartArtistProvider.GetArtistJsonPath(_config.CommonApplicationPaths, artistMusicBrainzId); var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); @@ -79,7 +82,7 @@ namespace MediaBrowser.Providers.Music try { - AddImages(list, artistXmlPath, musicBrainzId, musicBrainzReleaseGroupId, cancellationToken); + AddImages(list, artistJsonPath, musicBrainzId, musicBrainzReleaseGroupId, cancellationToken); } catch (FileNotFoundException) { @@ -124,217 +127,67 @@ namespace MediaBrowser.Providers.Music /// Adds the images. /// /// The list. - /// The XML path. + /// The path. /// The release identifier. /// The release group identifier. /// The cancellation token. - private void AddImages(List list, string xmlPath, string releaseId, string releaseGroupId, CancellationToken cancellationToken) + private void AddImages(List list, string path, string releaseId, string releaseGroupId, CancellationToken cancellationToken) { - using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8)) + var obj = _jsonSerializer.DeserializeFromFile(path); + + if (obj.albums != null) { - // Use XmlReader for best performance - using (var reader = XmlReader.Create(streamReader, new XmlReaderSettings + var album = obj.albums.FirstOrDefault(i => string.Equals(i.release_group_id, releaseGroupId, StringComparison.OrdinalIgnoreCase)); + + if (album != null) { - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true, - ValidationType = ValidationType.None - })) - { - reader.MoveToContent(); - - // Loop through each element - while (reader.Read()) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "music": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromMusicNode(list, releaseId, releaseGroupId, subReader, cancellationToken); - } - break; - } - - default: - reader.Skip(); - break; - } - } - } + PopulateImages(list, album.albumcover, ImageType.Primary, 1000, 1000); + PopulateImages(list, album.cdart, ImageType.Disc, 1000, 1000); } } } - /// - /// Adds the images from music node. - /// - /// The list. - /// The release identifier. - /// The release group identifier. - /// The reader. - /// The cancellation token. - private void AddImagesFromMusicNode(List list, string releaseId, string releaseGroupId, XmlReader reader, CancellationToken cancellationToken) + private void PopulateImages(List list, + List images, + ImageType type, + int width, + int height) { - reader.MoveToContent(); - - while (reader.Read()) + if (images == null) { - if (reader.NodeType == XmlNodeType.Element) + return; + } + + list.AddRange(images.Select(i => + { + var url = i.url; + + if (!string.IsNullOrEmpty(url)) { - switch (reader.Name) + var likesString = i.likes; + int likes; + + var info = new RemoteImageInfo { - case "albums": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromAlbumsNode(list, releaseId, releaseGroupId, subReader, cancellationToken); - } - break; - } - default: - { - using (reader.ReadSubtree()) - { - } - break; - } - } - } - } - } + RatingType = RatingType.Likes, + Type = type, + Width = width, + Height = height, + ProviderName = Name, + Url = url, + Language = i.lang + }; - /// - /// Adds the images from albums node. - /// - /// The list. - /// The release identifier. - /// The release group identifier. - /// The reader. - /// The cancellation token. - private void AddImagesFromAlbumsNode(List list, string releaseId, string releaseGroupId, XmlReader reader, CancellationToken cancellationToken) - { - reader.MoveToContent(); - - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) + if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) { - case "album": - { - var id = reader.GetAttribute("id"); - - using (var subReader = reader.ReadSubtree()) - { - if (string.Equals(id, releaseId, StringComparison.OrdinalIgnoreCase) || - string.Equals(id, releaseGroupId, StringComparison.OrdinalIgnoreCase)) - { - AddImages(list, subReader, cancellationToken); - } - } - break; - } - default: - { - using (reader.ReadSubtree()) - { - } - break; - } + info.CommunityRating = likes; } + + return info; } - } - } - /// - /// Adds the images. - /// - /// The list. - /// The reader. - /// The cancellation token. - private void AddImages(List list, XmlReader reader, CancellationToken cancellationToken) - { - reader.MoveToContent(); - - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "cdart": - { - AddImage(list, reader, ImageType.Disc, 1000, 1000); - break; - } - case "albumcover": - { - AddImage(list, reader, ImageType.Primary, 1000, 1000); - break; - } - default: - { - using (reader.ReadSubtree()) - { - } - break; - } - } - } - } - } - - /// - /// Adds the image. - /// - /// The list. - /// The reader. - /// The type. - /// The width. - /// The height. - private void AddImage(List list, XmlReader reader, ImageType type, int width, int height) - { - var url = reader.GetAttribute("url"); - - var size = reader.GetAttribute("size"); - - if (!string.IsNullOrEmpty(size)) - { - int sizeNum; - if (int.TryParse(size, NumberStyles.Any, _usCulture, out sizeNum)) - { - width = sizeNum; - height = sizeNum; - } - } - - var likesString = reader.GetAttribute("likes"); - int likes; - - var info = new RemoteImageInfo - { - RatingType = RatingType.Likes, - Type = type, - Width = width, - Height = height, - ProviderName = Name, - Url = url, - Language = reader.GetAttribute("lang") - }; - - if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) - { - info.CommunityRating = likes; - } - - list.Add(info); + return null; + }).Where(i => i != null)); } public int Order @@ -374,9 +227,9 @@ namespace MediaBrowser.Providers.Music if (!String.IsNullOrEmpty(artistMusicBrainzId)) { // Process images - var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId); + var artistJsonPath = FanartArtistProvider.GetArtistJsonPath(_config.CommonApplicationPaths, artistMusicBrainzId); - var fileInfo = _fileSystem.GetFileInfo(artistXmlPath); + var fileInfo = _fileSystem.GetFileInfo(artistJsonPath); return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; } diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 72e878366..ff95fcf20 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -14,11 +14,14 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using CommonIO; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Music { @@ -26,20 +29,22 @@ namespace MediaBrowser.Providers.Music { internal readonly SemaphoreSlim FanArtResourcePool = new SemaphoreSlim(3, 3); internal const string ApiKey = "5c6b04c68e904cfed1e6cbc9a9e683d4"; - private const string FanArtBaseUrl = "http://api.fanart.tv/webservice/artist/{0}/{1}/xml/all/1/1"; + private const string FanArtBaseUrl = "http://webservice.fanart.tv/v3.1/music/{1}?api_key={0}"; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IServerConfigurationManager _config; private readonly IHttpClient _httpClient; private readonly IFileSystem _fileSystem; + private readonly IJsonSerializer _jsonSerializer; internal static FanartArtistProvider Current; - public FanartArtistProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem) + public FanartArtistProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem, IJsonSerializer jsonSerializer) { _config = config; _httpClient = httpClient; _fileSystem = fileSystem; + _jsonSerializer = jsonSerializer; Current = this; } @@ -81,13 +86,13 @@ namespace MediaBrowser.Providers.Music if (!String.IsNullOrEmpty(artistMusicBrainzId)) { - await EnsureArtistXml(artistMusicBrainzId, cancellationToken).ConfigureAwait(false); + await EnsureArtistJson(artistMusicBrainzId, cancellationToken).ConfigureAwait(false); - var artistXmlPath = GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId); + var artistJsonPath = GetArtistJsonPath(_config.CommonApplicationPaths, artistMusicBrainzId); try { - AddImages(list, artistXmlPath, cancellationToken); + AddImages(list, artistJsonPath, cancellationToken); } catch (FileNotFoundException) { @@ -132,230 +137,62 @@ namespace MediaBrowser.Providers.Music /// Adds the images. /// /// The list. - /// The XML path. + /// The path. /// The cancellation token. - private void AddImages(List list, string xmlPath, CancellationToken cancellationToken) + private void AddImages(List list, string path, CancellationToken cancellationToken) { - using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8)) - { - // Use XmlReader for best performance - using (var reader = XmlReader.Create(streamReader, new XmlReaderSettings - { - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true, - ValidationType = ValidationType.None - })) - { - reader.MoveToContent(); + var obj = _jsonSerializer.DeserializeFromFile(path); - // Loop through each element - while (reader.Read()) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "music": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromMusicNode(list, subReader, cancellationToken); - } - break; - } - - default: - reader.Skip(); - break; - } - } - } - } - } + PopulateImages(list, obj.artistbackground, ImageType.Backdrop, 1920, 1080); + PopulateImages(list, obj.artistthumb, ImageType.Thumb, 500, 281); + PopulateImages(list, obj.hdmusiclogo, ImageType.Logo, 800, 310); + PopulateImages(list, obj.musicbanner, ImageType.Banner, 1000, 185); + PopulateImages(list, obj.musiclogo, ImageType.Logo, 400, 155); + PopulateImages(list, obj.hdmusicarts, ImageType.Art, 1000, 562); + PopulateImages(list, obj.musicarts, ImageType.Art, 500, 281); } - /// - /// Adds the images from music node. - /// - /// The list. - /// The reader. - /// The cancellation token. - private void AddImagesFromMusicNode(List list, XmlReader reader, CancellationToken cancellationToken) + private void PopulateImages(List list, + List images, + ImageType type, + int width, + int height) { - reader.MoveToContent(); - - while (reader.Read()) + if (images == null) { - if (reader.NodeType == XmlNodeType.Element) + return; + } + + list.AddRange(images.Select(i => + { + var url = i.url; + + if (!string.IsNullOrEmpty(url)) { - switch (reader.Name) + var likesString = i.likes; + int likes; + + var info = new RemoteImageInfo { - case "hdmusiclogos": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromImageTypeNode(list, ImageType.Logo, 800, 310, subReader, cancellationToken); - } - break; - } - case "musiclogos": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromImageTypeNode(list, ImageType.Logo, 400, 155, subReader, cancellationToken); - } - break; - } - case "artistbackgrounds": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromImageTypeNode(list, ImageType.Backdrop, 1920, 1080, subReader, cancellationToken); - } - break; - } - case "hdmusicarts": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromImageTypeNode(list, ImageType.Art, 1000, 562, subReader, cancellationToken); - } - break; - } - case "musicarts": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromImageTypeNode(list, ImageType.Art, 500, 281, subReader, cancellationToken); - } - break; - } - case "hdmusicbanners": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromImageTypeNode(list, ImageType.Banner, 1000, 185, subReader, cancellationToken); - } - break; - } - case "musicbanners": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromImageTypeNode(list, ImageType.Banner, 1000, 185, subReader, cancellationToken); - } - break; - } - case "artistthumbs": - { - using (var subReader = reader.ReadSubtree()) - { - AddImagesFromImageTypeNode(list, ImageType.Primary, 1000, 1000, subReader, cancellationToken); - } - break; - } - default: - { - using (reader.ReadSubtree()) - { - } - break; - } - } - } - } - } + RatingType = RatingType.Likes, + Type = type, + Width = width, + Height = height, + ProviderName = Name, + Url = url, + Language = i.lang + }; - /// - /// Adds the images from albums node. - /// - /// The list. - /// The type. - /// The width. - /// The height. - /// The reader. - /// The cancellation token. - private void AddImagesFromImageTypeNode(List list, ImageType type, int width, int height, XmlReader reader, CancellationToken cancellationToken) - { - reader.MoveToContent(); - - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) + if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) { - case "hdmusiclogo": - case "musiclogo": - case "artistbackground": - case "hdmusicart": - case "musicart": - case "hdmusicbanner": - case "musicbanner": - case "artistthumb": - { - AddImage(list, reader, type, width, height); - break; - } - default: - { - using (reader.ReadSubtree()) - { - } - break; - } + info.CommunityRating = likes; } + + return info; } - } - } - /// - /// Adds the image. - /// - /// The list. - /// The reader. - /// The type. - /// The width. - /// The height. - private void AddImage(List list, XmlReader reader, ImageType type, int width, int height) - { - var url = reader.GetAttribute("url"); - - var size = reader.GetAttribute("size"); - - if (!String.IsNullOrEmpty(size)) - { - int sizeNum; - if (Int32.TryParse(size, NumberStyles.Any, _usCulture, out sizeNum)) - { - width = sizeNum; - height = sizeNum; - } - } - - var likesString = reader.GetAttribute("likes"); - int likes; - - var info = new RemoteImageInfo - { - RatingType = RatingType.Likes, - Type = type, - Width = width, - Height = height, - ProviderName = Name, - Url = url, - Language = reader.GetAttribute("lang") - }; - - if (!String.IsNullOrEmpty(likesString) && Int32.TryParse(likesString, NumberStyles.Any, _usCulture, out likes)) - { - info.CommunityRating = likes; - } - - list.Add(info); + return null; + }).Where(i => i != null)); } public int Order @@ -386,9 +223,9 @@ namespace MediaBrowser.Providers.Music if (!String.IsNullOrEmpty(id)) { // Process images - var artistXmlPath = GetArtistXmlPath(_config.CommonApplicationPaths, id); + var artistJsonPath = GetArtistJsonPath(_config.CommonApplicationPaths, id); - var fileInfo = _fileSystem.GetFileInfo(artistXmlPath); + var fileInfo = _fileSystem.GetFileInfo(artistJsonPath); return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; } @@ -397,11 +234,11 @@ namespace MediaBrowser.Providers.Music } private readonly Task _cachedTask = Task.FromResult(true); - internal Task EnsureArtistXml(string musicBrainzId, CancellationToken cancellationToken) + internal Task EnsureArtistJson(string musicBrainzId, CancellationToken cancellationToken) { - var xmlPath = GetArtistXmlPath(_config.ApplicationPaths, musicBrainzId); + var jsonPath = GetArtistJsonPath(_config.ApplicationPaths, musicBrainzId); - var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath); + var fileInfo = _fileSystem.GetFileSystemInfo(jsonPath); if (fileInfo.Exists) { @@ -411,16 +248,16 @@ namespace MediaBrowser.Providers.Music } } - return DownloadArtistXml(musicBrainzId, cancellationToken); + return DownloadArtistJson(musicBrainzId, cancellationToken); } /// - /// Downloads the artist XML. + /// Downloads the artist data. /// /// The music brainz id. /// The cancellation token. /// Task{System.Boolean}. - internal async Task DownloadArtistXml(string musicBrainzId, CancellationToken cancellationToken) + internal async Task DownloadArtistJson(string musicBrainzId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -432,21 +269,35 @@ namespace MediaBrowser.Providers.Music url += "&client_key=" + clientKey; } - var xmlPath = GetArtistXmlPath(_config.ApplicationPaths, musicBrainzId); + var jsonPath = GetArtistJsonPath(_config.ApplicationPaths, musicBrainzId); - _fileSystem.CreateDirectory(Path.GetDirectoryName(xmlPath)); + _fileSystem.CreateDirectory(Path.GetDirectoryName(jsonPath)); - using (var response = await _httpClient.Get(new HttpRequestOptions + try { - Url = url, - ResourcePool = FanArtResourcePool, - CancellationToken = cancellationToken - - }).ConfigureAwait(false)) - { - using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + using (var response = await _httpClient.Get(new HttpRequestOptions { - await response.CopyToAsync(xmlFileStream).ConfigureAwait(false); + Url = url, + ResourcePool = FanArtResourcePool, + CancellationToken = cancellationToken + + }).ConfigureAwait(false)) + { + using (var saveFileStream = _fileSystem.GetFileStream(jsonPath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + { + await response.CopyToAsync(saveFileStream).ConfigureAwait(false); + } + } + } + catch (HttpException ex) + { + if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) + { + _jsonSerializer.SerializeToFile(new FanartArtistResponse(), jsonPath); + } + else + { + throw; } } } @@ -476,11 +327,43 @@ namespace MediaBrowser.Providers.Music return dataPath; } - internal static string GetArtistXmlPath(IApplicationPaths appPaths, string musicBrainzArtistId) + internal static string GetArtistJsonPath(IApplicationPaths appPaths, string musicBrainzArtistId) { var dataPath = GetArtistDataPath(appPaths, musicBrainzArtistId); - return Path.Combine(dataPath, "fanart.xml"); + return Path.Combine(dataPath, "fanart.json"); + } + + + public class FanartArtistImage + { + public string id { get; set; } + public string url { get; set; } + public string likes { get; set; } + public string disc { get; set; } + public string size { get; set; } + public string lang { get; set; } + } + + public class Album + { + public string release_group_id { get; set; } + public List cdart { get; set; } + public List albumcover { get; set; } + } + + public class FanartArtistResponse + { + public string name { get; set; } + public string mbid_id { get; set; } + public List artistthumb { get; set; } + public List artistbackground { get; set; } + public List hdmusiclogo { get; set; } + public List musicbanner { get; set; } + public List musiclogo { get; set; } + public List musicarts { get; set; } + public List hdmusicarts { get; set; } + public List albums { get; set; } } } } diff --git a/MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs b/MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs index c1a2a3f18..425799384 100644 --- a/MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs +++ b/MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.Providers.Music var path = FanartArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths); - _fileSystem.CreateDirectory(path); + _fileSystem.CreateDirectory(path); var timestampFile = Path.Combine(path, "time.txt"); @@ -79,7 +79,7 @@ namespace MediaBrowser.Providers.Music } // Find out the last time we queried for updates - var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty; + var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty; var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList(); @@ -94,8 +94,8 @@ namespace MediaBrowser.Providers.Music } var newUpdateTime = Convert.ToInt64(DateTimeToUnixTimestamp(DateTime.UtcNow)).ToString(UsCulture); - - _fileSystem.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8); + + _fileSystem.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8); progress.Report(100); } @@ -115,7 +115,7 @@ namespace MediaBrowser.Providers.Music { url += "&client_key=" + options.UserApiKey; } - + // First get last time using (var stream = await _httpClient.Get(new HttpRequestOptions { @@ -180,7 +180,7 @@ namespace MediaBrowser.Providers.Music { _logger.Info("Updating artist " + musicBrainzId); - return FanartArtistProvider.Current.DownloadArtistXml(musicBrainzId, cancellationToken); + return FanartArtistProvider.Current.DownloadArtistJson(musicBrainzId, cancellationToken); } ///