From 4039c0f70483bbd4294e07126716c626747d86d4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 6 Sep 2013 11:23:20 -0400 Subject: [PATCH] fixes #521 - Use last fm as a secondary provider for artist images --- .../Entities/Audio/Artist.cs | 2 + .../Entities/Audio/MusicArtist.cs | 1 + .../MediaBrowser.Providers.csproj | 1 + .../Music/ArtistsPostScanTask.cs | 29 ++--- .../Music/FanArtArtistProvider.cs | 8 ++ .../Music/LastFmArtistImageProvider.cs | 120 ++++++++++++++++++ .../Music/LastfmAlbumProvider.cs | 6 +- .../Music/LastfmArtistByNameProvider.cs | 6 +- .../Music/LastfmArtistProvider.cs | 12 +- .../Music/LastfmBaseProvider.cs | 19 ++- MediaBrowser.Providers/Music/LastfmHelper.cs | 34 +++++ 11 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 MediaBrowser.Providers/Music/LastFmArtistImageProvider.cs diff --git a/MediaBrowser.Controller/Entities/Audio/Artist.cs b/MediaBrowser.Controller/Entities/Audio/Artist.cs index 7730c90d3..274356b30 100644 --- a/MediaBrowser.Controller/Entities/Audio/Artist.cs +++ b/MediaBrowser.Controller/Entities/Audio/Artist.cs @@ -6,6 +6,8 @@ namespace MediaBrowser.Controller.Entities.Audio /// public class Artist : BaseItem, IItemByName { + public string LastFmImageUrl { get; set; } + /// /// Gets the user data key. /// diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index b2fc04873..e2bad64d4 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -6,5 +6,6 @@ namespace MediaBrowser.Controller.Entities.Audio /// public class MusicArtist : Folder { + public string LastFmImageUrl { get; set; } } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 9d0739494..c49424f65 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -77,6 +77,7 @@ + diff --git a/MediaBrowser.Providers/Music/ArtistsPostScanTask.cs b/MediaBrowser.Providers/Music/ArtistsPostScanTask.cs index 58da610a6..f67d61681 100644 --- a/MediaBrowser.Providers/Music/ArtistsPostScanTask.cs +++ b/MediaBrowser.Providers/Music/ArtistsPostScanTask.cs @@ -63,28 +63,17 @@ namespace MediaBrowser.Providers.Music backdrops.InsertRange(0, artist.BackdropImagePaths); artist.BackdropImagePaths = backdrops.Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - - if (!artist.LockedFields.Contains(MetadataFields.Genres)) - { - // Merge genres - var genres = musicArtist.Genres.ToList(); - genres.InsertRange(0, artist.Genres); - artist.Genres = genres.Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } } - else - { - if (!artist.LockedFields.Contains(MetadataFields.Genres)) - { - // Avoid implicitly captured closure - var artist1 = artist; - artist.Genres = allSongs.Where(i => i.HasArtist(artist1.Name)) - .SelectMany(i => i.Genres) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } + if (!artist.LockedFields.Contains(MetadataFields.Genres)) + { + // Avoid implicitly captured closure + var artist1 = artist; + + artist.Genres = allSongs.Where(i => i.HasArtist(artist1.Name)) + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); } numComplete++; diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 2593b9838..9960ebc91 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -115,6 +115,14 @@ namespace MediaBrowser.Providers.Music } } + public override MetadataProviderPriority Priority + { + get + { + return MetadataProviderPriority.Fourth; + } + } + /// /// Needses the refresh internal. /// diff --git a/MediaBrowser.Providers/Music/LastFmArtistImageProvider.cs b/MediaBrowser.Providers/Music/LastFmArtistImageProvider.cs new file mode 100644 index 000000000..b5fb160c3 --- /dev/null +++ b/MediaBrowser.Providers/Music/LastFmArtistImageProvider.cs @@ -0,0 +1,120 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + /// + /// Class LastFmArtistImageProvider + /// + public class LastFmArtistImageProvider : BaseMetadataProvider + { + /// + /// The _provider manager + /// + private readonly IProviderManager _providerManager; + + /// + /// Initializes a new instance of the class. + /// + /// The log manager. + /// The configuration manager. + /// The provider manager. + public LastFmArtistImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : + base(logManager, configurationManager) + { + _providerManager = providerManager; + } + + /// + /// Supportses the specified item. + /// + /// The item. + /// true if XXXX, false otherwise + public override bool Supports(BaseItem item) + { + return item is Artist || item is MusicArtist; + } + + /// + /// Needses the refresh internal. + /// + /// The item. + /// The provider info. + /// true if XXXX, false otherwise + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + if (item.HasImage(ImageType.Primary)) + { + return false; + } + + if (string.IsNullOrWhiteSpace(GetImageUrl(item))) + { + return false; + } + + return base.NeedsRefreshInternal(item, providerInfo); + } + + /// + /// Fetches metadata and returns true or false indicating if any work that requires persistence was done + /// + /// The item. + /// if set to true [force]. + /// The cancellation token. + /// Task{System.Boolean}. + public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + { + var url = GetImageUrl(item); + + if (!string.IsNullOrWhiteSpace(url)) + { + await _providerManager.SaveImage(item, url, LastfmBaseProvider.LastfmResourcePool, ImageType.Primary, null, cancellationToken) + .ConfigureAwait(false); + } + + SetLastRefreshed(item, DateTime.UtcNow); + return true; + } + + /// + /// Gets the priority. + /// + /// The priority. + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Fifth; } + } + + /// + /// Gets the image URL. + /// + /// The item. + /// System.String. + private string GetImageUrl(BaseItem item) + { + var musicArtist = item as MusicArtist; + + if (musicArtist != null) + { + return musicArtist.LastFmImageUrl; + } + + var artistByName = item as Artist; + + if (artistByName != null) + { + return artistByName.LastFmImageUrl; + } + + return null; + } + } +} diff --git a/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs b/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs index 50a5ac8ed..275b3c0af 100644 --- a/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs @@ -67,7 +67,7 @@ namespace MediaBrowser.Providers.Music return base.NeedsRefreshInternal(item, providerInfo); } - protected override async Task FetchLastfmData(BaseItem item, string id, CancellationToken cancellationToken) + protected override async Task FetchLastfmData(BaseItem item, string id, bool force, CancellationToken cancellationToken) { var album = (MusicAlbum)item; @@ -165,9 +165,9 @@ namespace MediaBrowser.Providers.Music } } - protected override Task FetchData(BaseItem item, CancellationToken cancellationToken) + protected override Task FetchData(BaseItem item, bool force, CancellationToken cancellationToken) { - return FetchLastfmData(item, string.Empty, cancellationToken); + return FetchLastfmData(item, string.Empty, force, cancellationToken); } public override bool Supports(BaseItem item) diff --git a/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs b/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs index d3bc28d3c..5bc9debd3 100644 --- a/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs @@ -72,20 +72,20 @@ namespace MediaBrowser.Providers.Music /// The music brainz id. /// The cancellation token. /// Task. - protected override async Task FetchLastfmData(BaseItem item, string musicBrainzId, CancellationToken cancellationToken) + protected override async Task FetchLastfmData(BaseItem item, string musicBrainzId, bool force, CancellationToken cancellationToken) { var artist = (Artist)item; // See if we can avoid an http request by finding the matching MusicArtist entity var musicArtist = FindMusicArtist(artist, LibraryManager); - if (musicArtist != null) + if (musicArtist != null && !force) { LastfmHelper.ProcessArtistData(musicArtist, artist); } else { - await base.FetchLastfmData(item, musicBrainzId, cancellationToken).ConfigureAwait(false); + await base.FetchLastfmData(item, musicBrainzId, force, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs index bbd2325b4..a65cecb88 100644 --- a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs @@ -200,7 +200,7 @@ namespace MediaBrowser.Providers.Music /// The music brainz id. /// The cancellation token. /// Task. - protected override async Task FetchLastfmData(BaseItem item, string musicBrainzId, CancellationToken cancellationToken) + protected override async Task FetchLastfmData(BaseItem item, string musicBrainzId, bool force, CancellationToken cancellationToken) { // Get artist info with provided id var url = RootUrl + string.Format("method=artist.getInfo&mbid={0}&api_key={1}&format=json", UrlEncode(musicBrainzId), ApiKey); @@ -216,7 +216,15 @@ namespace MediaBrowser.Providers.Music }).ConfigureAwait(false)) { - result = JsonSerializer.DeserializeFromStream(json); + using (var reader = new StreamReader(json)) + { + var jsonText = await reader.ReadToEndAsync().ConfigureAwait(false); + + // Fix their bad json + jsonText = jsonText.Replace("\"#text\"", "\"url\""); + + result = JsonSerializer.DeserializeFromString(jsonText); + } } if (result != null && result.artist != null) diff --git a/MediaBrowser.Providers/Music/LastfmBaseProvider.cs b/MediaBrowser.Providers/Music/LastfmBaseProvider.cs index d5580bb4d..36099509d 100644 --- a/MediaBrowser.Providers/Music/LastfmBaseProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmBaseProvider.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.Music /// public abstract class LastfmBaseProvider : BaseMetadataProvider { - protected static readonly SemaphoreSlim LastfmResourcePool = new SemaphoreSlim(4, 4); + internal static readonly SemaphoreSlim LastfmResourcePool = new SemaphoreSlim(4, 4); /// /// Initializes a new instance of the class. @@ -100,7 +100,7 @@ namespace MediaBrowser.Providers.Music /// The item. /// /// Task. - protected virtual async Task FetchData(BaseItem item, CancellationToken cancellationToken) + protected virtual async Task FetchData(BaseItem item, bool force, CancellationToken cancellationToken) { var id = item.GetProviderId(MetadataProviders.Musicbrainz) ?? await FindId(item, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(id)) @@ -111,18 +111,18 @@ namespace MediaBrowser.Providers.Music item.SetProviderId(MetadataProviders.Musicbrainz, id); - await FetchLastfmData(item, id, cancellationToken).ConfigureAwait(false); + await FetchLastfmData(item, id, force, cancellationToken).ConfigureAwait(false); } else { Logger.Info("LastfmProvider could not find " + item.Name + ". Check name on Last.fm."); } - + } protected abstract Task FindId(BaseItem item, CancellationToken cancellationToken); - protected abstract Task FetchLastfmData(BaseItem item, string id, CancellationToken cancellationToken); + protected abstract Task FetchLastfmData(BaseItem item, string id, bool force, CancellationToken cancellationToken); /// /// Encodes an URL. @@ -145,7 +145,7 @@ namespace MediaBrowser.Providers.Music { cancellationToken.ThrowIfCancellationRequested(); - await FetchData(item, cancellationToken).ConfigureAwait(false); + await FetchData(item, force, cancellationToken).ConfigureAwait(false); SetLastRefreshed(item, DateTime.UtcNow); return true; } @@ -187,6 +187,12 @@ namespace MediaBrowser.Providers.Music public List formationlist { get; set; } } + public class LastFmImage + { + public string url { get; set; } + public string size { get; set; } + } + public class LastfmArtist { public string name { get; set; } @@ -198,6 +204,7 @@ namespace MediaBrowser.Providers.Music public List similar { get; set; } public LastfmTags tags { get; set; } public LastFmBio bio { get; set; } + public List image { get; set; } } diff --git a/MediaBrowser.Providers/Music/LastfmHelper.cs b/MediaBrowser.Providers/Music/LastfmHelper.cs index 56f18d7e6..a955ecbd5 100644 --- a/MediaBrowser.Providers/Music/LastfmHelper.cs +++ b/MediaBrowser.Providers/Music/LastfmHelper.cs @@ -31,6 +31,40 @@ namespace MediaBrowser.Providers.Music { AddTags(artist, data.tags); } + + var musicArtist = artist as MusicArtist; + + if (musicArtist != null) + { + musicArtist.LastFmImageUrl = GetImageUrl(data); + } + + var artistByName = artist as Artist; + + if (artistByName != null) + { + artistByName.LastFmImageUrl = GetImageUrl(data); + } + } + + private static string GetImageUrl(LastfmArtist data) + { + if (data.image == null) + { + return null; + } + + var img = data.image.FirstOrDefault(i => string.Equals(i.size, "extralarge", StringComparison.OrdinalIgnoreCase)) ?? + data.image.FirstOrDefault(i => string.Equals(i.size, "large", StringComparison.OrdinalIgnoreCase)) ?? + data.image.FirstOrDefault(i => string.Equals(i.size, "medium", StringComparison.OrdinalIgnoreCase)) ?? + data.image.FirstOrDefault(); + + if (img != null) + { + return img.url; + } + + return null; } public static void ProcessArtistData(MusicArtist source, Artist target)