From 33c0b0e99c3df88906aac2f9fc2b363105bff0b1 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 5 Nov 2013 17:29:07 -0500 Subject: [PATCH] #609 - Add manual image selection for Artists --- .../MediaBrowser.Providers.csproj | 1 + .../Music/FanArtAlbumProvider.cs | 4 +- .../Music/FanArtArtistProvider.cs | 218 +++-------- .../Music/ManualFanartAlbumProvider.cs | 14 + .../Music/ManualFanartArtistProvider.cs | 340 ++++++++++++++++++ 5 files changed, 412 insertions(+), 165 deletions(-) create mode 100644 MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 5fa3a6296..f4d0ad7eb 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -91,6 +91,7 @@ + diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index b3ab4c367..2ec180574 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -172,7 +172,7 @@ namespace MediaBrowser.Providers.Music { cancellationToken.ThrowIfCancellationRequested(); - if (ConfigurationManager.Configuration.DownloadSeriesImages.Primary && !item.HasImage(ImageType.Primary)) + if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary && !item.HasImage(ImageType.Primary)) { var image = images.FirstOrDefault(i => i.Type == ImageType.Primary); @@ -184,7 +184,7 @@ namespace MediaBrowser.Providers.Music cancellationToken.ThrowIfCancellationRequested(); - if (ConfigurationManager.Configuration.DownloadSeriesImages.Disc && !item.HasImage(ImageType.Disc)) + if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Disc && !item.HasImage(ImageType.Disc)) { var image = images.FirstOrDefault(i => i.Type == ImageType.Disc); diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index b1d97d8b5..51adbc785 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -4,13 +4,14 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Providers; using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -139,50 +140,25 @@ namespace MediaBrowser.Providers.Music return false; } - if (!ConfigurationManager.Configuration.DownloadMusicArtistImages.Art && - !ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops && - !ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner && - !ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo && - !ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary && - - // The fanart album provider depends on xml downloaded here, so honor it's settings too - !ConfigurationManager.Configuration.DownloadMusicAlbumImages.Disc && - !ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary) - { - return false; - } - return base.NeedsRefreshInternal(item, providerInfo); } - protected override DateTime CompareDate(BaseItem item) + protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) { var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz); if (!string.IsNullOrEmpty(musicBrainzId)) { // Process images - var path = GetArtistDataPath(ConfigurationManager.ApplicationPaths, musicBrainzId); + var artistXmlPath = GetArtistDataPath(ConfigurationManager.CommonApplicationPaths, musicBrainzId); + artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml"); - try - { - var files = new DirectoryInfo(path) - .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly) - .Select(i => _fileSystem.GetLastWriteTimeUtc(i)) - .ToList(); + var file = new FileInfo(artistXmlPath); - if (files.Count > 0) - { - return files.Max(); - } - } - catch (DirectoryNotFoundException) - { - - } + return !file.Exists || _fileSystem.GetLastWriteTimeUtc(file) > providerInfo.LastRefreshed; } - return base.CompareDate(item); + return base.NeedsRefreshBasedOnCompareDate(item, providerInfo); } /// @@ -191,22 +167,22 @@ namespace MediaBrowser.Providers.Music protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); /// - /// Gets the series data path. + /// Gets the artist data path. /// - /// The app paths. - /// The music brainz artist id. + /// The application paths. + /// The music brainz artist identifier. /// System.String. internal static string GetArtistDataPath(IApplicationPaths appPaths, string musicBrainzArtistId) { - var seriesDataPath = Path.Combine(GetArtistDataPath(appPaths), musicBrainzArtistId); + var dataPath = Path.Combine(GetArtistDataPath(appPaths), musicBrainzArtistId); - return seriesDataPath; + return dataPath; } /// - /// Gets the series data path. + /// Gets the artist data path. /// - /// The app paths. + /// The application paths. /// System.String. internal static string GetArtistDataPath(IApplicationPaths appPaths) { @@ -243,17 +219,9 @@ namespace MediaBrowser.Providers.Music ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo || ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary) { - if (File.Exists(xmlPath)) - { - await FetchFromXml(item, xmlPath, cancellationToken).ConfigureAwait(false); - } - } + var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartArtistProvider.ProviderName).ConfigureAwait(false); - BaseProviderInfo data; - if (!item.ProviderData.TryGetValue(Id, out data)) - { - data = new BaseProviderInfo(); - item.ProviderData[Id] = data; + await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false); } SetLastRefreshed(item, DateTime.UtcNow); @@ -296,151 +264,75 @@ namespace MediaBrowser.Providers.Music /// Fetches from XML. /// /// The item. - /// The XML file path. + /// The images. /// The cancellation token. /// Task. - private async Task FetchFromXml(BaseItem item, string xmlFilePath, CancellationToken cancellationToken) + private async Task FetchFromXml(BaseItem item, List images , CancellationToken cancellationToken) { - var doc = new XmlDocument(); - doc.Load(xmlFilePath); - cancellationToken.ThrowIfCancellationRequested(); - string path; + if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary && !item.HasImage(ImageType.Primary)) + { + var image = images.FirstOrDefault(i => i.Type == ImageType.Primary); + + if (image != null) + { + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); + } + } + + cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo && !item.HasImage(ImageType.Logo)) { - var node = - doc.SelectSingleNode("//fanart/music/hdmusiclogos/hdmusiclogo/@url") ?? - doc.SelectSingleNode("//fanart/music/musiclogos/musiclogo/@url"); - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Logo); + + if (image != null) { - try - { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Logo, null, cancellationToken) - .ConfigureAwait(false); - } - catch (HttpException ex) - { - // Sometimes fanart has bad url's in their xml. Nothing we can do here but catch it - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } - } + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Logo, null, cancellationToken).ConfigureAwait(false); } } - cancellationToken.ThrowIfCancellationRequested(); - - var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; - - if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops && item.BackdropImagePaths.Count == 0) - { - var nodes = doc.SelectNodes("//fanart/music/artistbackgrounds//@url"); - if (nodes != null) - { - var numBackdrops = 0; - - foreach (XmlNode node in nodes) - { - path = node.Value; - if (!string.IsNullOrEmpty(path) && !item.ContainsImageWithSourceUrl(path)) - { - try - { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) - .ConfigureAwait(false); - numBackdrops++; - if (numBackdrops >= backdropLimit) break; - } - catch (HttpException ex) - { - // Sometimes fanart has bad url's in their xml. Nothing we can do here but catch it - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } - } - } - } - - } - - } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art && !item.HasImage(ImageType.Art)) { - var node = - doc.SelectSingleNode("//fanart/music/hdmusicarts/hdmusicart/@url") ?? - doc.SelectSingleNode("//fanart/music/musicarts/musicart/@url"); - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Art); + + if (image != null) { - try - { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Art, null, cancellationToken) - .ConfigureAwait(false); - } - catch (HttpException ex) - { - // Sometimes fanart has bad url's in their xml. Nothing we can do here but catch it - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } - } + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Art, null, cancellationToken).ConfigureAwait(false); } } + cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner && !item.HasImage(ImageType.Banner)) { - var node = doc.SelectSingleNode("//fanart/music/hdmusicbanners/hdmusicbanner/@url") ?? - doc.SelectSingleNode("//fanart/music/musicbanners/musicbanner/@url"); - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var image = images.FirstOrDefault(i => i.Type == ImageType.Banner); + + if (image != null) { - try - { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Banner, null, cancellationToken) - .ConfigureAwait(false); - } - catch (HttpException ex) - { - // Sometimes fanart has bad url's in their xml. Nothing we can do here but catch it - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } - } + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Banner, null, cancellationToken).ConfigureAwait(false); } } cancellationToken.ThrowIfCancellationRequested(); - // Artist thumbs are actually primary images (they are square/portrait) - if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary && !item.HasImage(ImageType.Primary)) + var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; + if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops && + item.BackdropImagePaths.Count < backdropLimit) { - var node = doc.SelectSingleNode("//fanart/music/artistthumbs/artistthumb/@url"); - path = node != null ? node.Value : null; - if (!string.IsNullOrEmpty(path)) + var numBackdrops = item.BackdropImagePaths.Count; + + foreach (var image in images.Where(i => i.Type == ImageType.Backdrop)) { - try - { - await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Primary, null, cancellationToken) - .ConfigureAwait(false); - } - catch (HttpException ex) - { - // Sometimes fanart has bad url's in their xml. Nothing we can do here but catch it - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } - } + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) + .ConfigureAwait(false); + + numBackdrops++; + + if (item.BackdropImagePaths.Count >= backdropLimit) break; } } } diff --git a/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs b/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs index a7f4428ff..bd79da15c 100644 --- a/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs @@ -239,6 +239,12 @@ namespace MediaBrowser.Providers.Music } } + /// + /// Adds the images. + /// + /// The list. + /// The reader. + /// The cancellation token. private void AddImages(List list, XmlReader reader, CancellationToken cancellationToken) { reader.MoveToContent(); @@ -271,6 +277,14 @@ namespace MediaBrowser.Providers.Music } } + /// + /// 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"); diff --git a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs new file mode 100644 index 000000000..2e4dbe813 --- /dev/null +++ b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs @@ -0,0 +1,340 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; + +namespace MediaBrowser.Providers.Music +{ + public class ManualFanartArtistProvider : IImageProvider + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly IServerConfigurationManager _config; + + public ManualFanartArtistProvider(IServerConfigurationManager config) + { + _config = config; + } + + public string Name + { + get { return ProviderName; } + } + + public static string ProviderName + { + get { return "FanArt"; } + } + + public bool Supports(BaseItem item) + { + return item is MusicArtist || item is Artist; + } + + public async Task> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) + { + var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); + + return images.Where(i => i.Type == imageType); + } + + public Task> GetAllImages(BaseItem item, CancellationToken cancellationToken) + { + var list = new List(); + + var artistMusicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz); + + if (!string.IsNullOrEmpty(artistMusicBrainzId)) + { + var artistXmlPath = FanArtArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths, artistMusicBrainzId); + artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml"); + + try + { + AddImages(list, artistXmlPath, cancellationToken); + } + catch (FileNotFoundException) + { + + } + } + + var language = _config.Configuration.PreferredMetadataLanguage; + + var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase); + + // Sort first by width to prioritize HD versions + list = list.OrderByDescending(i => i.Width ?? 0) + .ThenByDescending(i => + { + if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 3; + } + if (!isLanguageEn) + { + if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 2; + } + } + if (string.IsNullOrEmpty(i.Language)) + { + return isLanguageEn ? 3 : 2; + } + return 0; + }) + .ThenByDescending(i => i.CommunityRating ?? 0) + .ThenByDescending(i => i.VoteCount ?? 0) + .ToList(); + + return Task.FromResult>(list); + } + + /// + /// Adds the images. + /// + /// The list. + /// The XML path. + /// The cancellation token. + private void AddImages(List list, string xmlPath, 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(); + + // 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; + } + } + } + } + } + } + + /// + /// Adds the images from music node. + /// + /// The list. + /// The reader. + /// The cancellation token. + private void AddImagesFromMusicNode(List list, XmlReader reader, CancellationToken cancellationToken) + { + reader.MoveToContent(); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + 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; + } + } + } + } + } + + /// + /// 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) + { + 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; + } + } + } + } + } + + /// + /// 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); + } + + public int Priority + { + get { return 0; } + } + } +}