diff --git a/MediaBrowser.Providers/ImagesByName/GenreImageProvider.cs b/MediaBrowser.Providers/ImagesByName/GenreImageProvider.cs new file mode 100644 index 0000000000..cc9b4d4cfd --- /dev/null +++ b/MediaBrowser.Providers/ImagesByName/GenreImageProvider.cs @@ -0,0 +1,154 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +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.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.ImagesByName +{ + public class GenreImageProvider : BaseMetadataProvider + { + private readonly IProviderManager _providerManager; + private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(5, 5); + + public GenreImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) + : base(logManager, configurationManager) + { + _providerManager = providerManager; + } + + public override bool Supports(BaseItem item) + { + return item is Genre; + } + + public override bool RequiresInternet + { + get + { + return true; + } + } + + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Thumb)) + { + return false; + } + + return base.NeedsRefreshInternal(item, providerInfo); + } + + protected override bool RefreshOnVersionChange + { + get + { + return true; + } + } + + protected override string ProviderVersion + { + get + { + return "6"; + } + } + + public override async Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + { + if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Thumb)) + { + SetLastRefreshed(item, DateTime.UtcNow, providerInfo); + return true; + } + + var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, GenresManualImageProvider.ProviderName).ConfigureAwait(false); + + await DownloadImages(item, images.ToList(), cancellationToken).ConfigureAwait(false); + + SetLastRefreshed(item, DateTime.UtcNow, providerInfo); + return true; + } + + private async Task DownloadImages(BaseItem item, List images, CancellationToken cancellationToken) + { + if (!item.LockedFields.Contains(MetadataFields.Images)) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (!item.HasImage(ImageType.Primary)) + { + await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false); + } + cancellationToken.ThrowIfCancellationRequested(); + + if (!item.HasImage(ImageType.Thumb)) + { + await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false); + } + } + + if (!item.LockedFields.Contains(MetadataFields.Backdrops)) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (item.BackdropImagePaths.Count == 0) + { + foreach (var image in images.Where(i => i.Type == ImageType.Backdrop)) + { + await _providerManager.SaveImage(item, image.Url, _resourcePool, ImageType.Backdrop, null, cancellationToken) + .ConfigureAwait(false); + + break; + } + } + } + } + + + private async Task SaveImage(BaseItem item, IEnumerable images, ImageType type, CancellationToken cancellationToken) + { + foreach (var image in images.Where(i => i.Type == type)) + { + try + { + await _providerManager.SaveImage(item, image.Url, _resourcePool, type, null, cancellationToken).ConfigureAwait(false); + break; + } + catch (HttpException ex) + { + // Sometimes fanart has bad url's in their xml + if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) + { + continue; + } + break; + } + } + } + + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Third; } + } + } +} diff --git a/MediaBrowser.Providers/ImagesByName/GenresManualImageProvider.cs b/MediaBrowser.Providers/ImagesByName/GenresManualImageProvider.cs new file mode 100644 index 0000000000..b8bea4acff --- /dev/null +++ b/MediaBrowser.Providers/ImagesByName/GenresManualImageProvider.cs @@ -0,0 +1,128 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.ImagesByName +{ + public class GenresManualImageProvider : IImageProvider + { + private readonly IServerConfigurationManager _config; + private readonly IHttpClient _httpClient; + private readonly IFileSystem _fileSystem; + + private readonly SemaphoreSlim _listResourcePool = new SemaphoreSlim(1, 1); + + public GenresManualImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem) + { + _config = config; + _httpClient = httpClient; + _fileSystem = fileSystem; + } + + public string Name + { + get { return ProviderName; } + } + + public static string ProviderName + { + get { return "Media Browser"; } + } + + public bool Supports(IHasImages item) + { + return item is Genre; + } + + public Task> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) + { + return GetImages(item, imageType == ImageType.Primary, imageType == ImageType.Backdrop, cancellationToken); + } + + public Task> GetAllImages(IHasImages item, CancellationToken cancellationToken) + { + return GetImages(item, true, true, cancellationToken); + } + + private async Task> GetImages(IHasImages item, bool posters, bool backdrops, CancellationToken cancellationToken) + { + var list = new List(); + + if (posters) + { + var posterPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotegenreposters.txt"); + + await EnsurePosterList(posterPath, cancellationToken).ConfigureAwait(false); + + list.Add(GetImage(item, posterPath, ImageType.Primary, "folder")); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (backdrops) + { + var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotegenrethumbs.txt"); + + await EnsureThumbsList(thumbsPath, cancellationToken).ConfigureAwait(false); + + list.Add(GetImage(item, thumbsPath, ImageType.Thumb, "thumb")); + } + + return list.Where(i => i != null); + } + + private RemoteImageInfo GetImage(IHasImages item, string filename, ImageType type, string remoteFilename) + { + var list = ImageUtils.GetAvailableImages(filename); + + var match = ImageUtils.FindMatch(item, list); + + if (!string.IsNullOrEmpty(match)) + { + var url = GetUrl(match, remoteFilename); + + return new RemoteImageInfo + { + ProviderName = Name, + Type = type, + Url = url + }; + } + + return null; + } + + private string GetUrl(string image, string filename) + { + return string.Format("https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/genres/{0}/{1}.jpg", image, filename); + } + + private Task EnsureThumbsList(string file, CancellationToken cancellationToken) + { + const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/genrethumbs.txt"; + + return ImageUtils.EnsureList(url, file, _httpClient, _fileSystem, _listResourcePool, cancellationToken); + } + + private Task EnsurePosterList(string file, CancellationToken cancellationToken) + { + const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/genreposters.txt"; + + return ImageUtils.EnsureList(url, file, _httpClient, _fileSystem, _listResourcePool, cancellationToken); + } + + public int Priority + { + get { return 0; } + } + } +} diff --git a/MediaBrowser.Providers/ImagesByName/ImageUtils.cs b/MediaBrowser.Providers/ImagesByName/ImageUtils.cs new file mode 100644 index 0000000000..cee8c9ded2 --- /dev/null +++ b/MediaBrowser.Providers/ImagesByName/ImageUtils.cs @@ -0,0 +1,83 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.ImagesByName +{ + public static class ImageUtils + { + /// + /// Ensures the list. + /// + /// The URL. + /// The file. + /// The HTTP client. + /// The file system. + /// The semaphore. + /// The cancellation token. + /// Task. + public static async Task EnsureList(string url, string file, IHttpClient httpClient, IFileSystem fileSystem, SemaphoreSlim semaphore, CancellationToken cancellationToken) + { + var fileInfo = new FileInfo(file); + + if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1) + { + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + var temp = await httpClient.GetTempFile(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Progress = new Progress(), + Url = url + + }).ConfigureAwait(false); + + Directory.CreateDirectory(Path.GetDirectoryName(file)); + + File.Copy(temp, file, true); + } + finally + { + semaphore.Release(); + } + } + } + + public static string FindMatch(IHasImages item, IEnumerable images) + { + var name = GetComparableName(item.Name); + + return images.FirstOrDefault(i => string.Equals(name, GetComparableName(i), StringComparison.OrdinalIgnoreCase)); + } + + private static string GetComparableName(string name) + { + return name.Replace(" ", string.Empty).Replace(".", string.Empty).Replace("&", string.Empty).Replace("!", string.Empty); + } + + public static IEnumerable GetAvailableImages(string file) + { + using (var reader = new StreamReader(file)) + { + var lines = new List(); + + while (!reader.EndOfStream) + { + var text = reader.ReadLine(); + + lines.Add(text); + } + + return lines; + } + } + } +} diff --git a/MediaBrowser.Providers/Studios/StudioImageProvider.cs b/MediaBrowser.Providers/ImagesByName/StudioImageProvider.cs similarity index 98% rename from MediaBrowser.Providers/Studios/StudioImageProvider.cs rename to MediaBrowser.Providers/ImagesByName/StudioImageProvider.cs index 6d8d023dbd..11a3a7a674 100644 --- a/MediaBrowser.Providers/Studios/StudioImageProvider.cs +++ b/MediaBrowser.Providers/ImagesByName/StudioImageProvider.cs @@ -13,7 +13,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -namespace MediaBrowser.Providers.Studios +namespace MediaBrowser.Providers.ImagesByName { public class StudioImageProvider : BaseMetadataProvider { @@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.Studios { get { - return "5"; + return "6"; } } diff --git a/MediaBrowser.Providers/ImagesByName/StudiosManualImageProvider.cs b/MediaBrowser.Providers/ImagesByName/StudiosManualImageProvider.cs new file mode 100644 index 0000000000..8cabd0c841 --- /dev/null +++ b/MediaBrowser.Providers/ImagesByName/StudiosManualImageProvider.cs @@ -0,0 +1,128 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.ImagesByName +{ + public class StudiosManualImageProvider : IImageProvider + { + private readonly IServerConfigurationManager _config; + private readonly IHttpClient _httpClient; + private readonly IFileSystem _fileSystem; + + private readonly SemaphoreSlim _listResourcePool = new SemaphoreSlim(1, 1); + + public StudiosManualImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem) + { + _config = config; + _httpClient = httpClient; + _fileSystem = fileSystem; + } + + public string Name + { + get { return ProviderName; } + } + + public static string ProviderName + { + get { return "Media Browser"; } + } + + public bool Supports(IHasImages item) + { + return item is Studio; + } + + public Task> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) + { + return GetImages(item, imageType == ImageType.Primary, imageType == ImageType.Backdrop, cancellationToken); + } + + public Task> GetAllImages(IHasImages item, CancellationToken cancellationToken) + { + return GetImages(item, true, true, cancellationToken); + } + + private async Task> GetImages(IHasImages item, bool posters, bool backdrops, CancellationToken cancellationToken) + { + var list = new List(); + + if (posters) + { + var posterPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudioposters.txt"); + + await EnsurePosterList(posterPath, cancellationToken).ConfigureAwait(false); + + list.Add(GetImage(item, posterPath, ImageType.Primary, "folder")); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (backdrops) + { + var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudiothumbs.txt"); + + await EnsureThumbsList(thumbsPath, cancellationToken).ConfigureAwait(false); + + list.Add(GetImage(item, thumbsPath, ImageType.Thumb, "thumb")); + } + + return list.Where(i => i != null); + } + + private RemoteImageInfo GetImage(IHasImages item, string filename, ImageType type, string remoteFilename) + { + var list = ImageUtils.GetAvailableImages(filename); + + var match = ImageUtils.FindMatch(item, list); + + if (!string.IsNullOrEmpty(match)) + { + var url = GetUrl(match, remoteFilename); + + return new RemoteImageInfo + { + ProviderName = Name, + Type = type, + Url = url + }; + } + + return null; + } + + private string GetUrl(string image, string filename) + { + return string.Format("https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studios/{0}/{1}.jpg", image, filename); + } + + private Task EnsureThumbsList(string file, CancellationToken cancellationToken) + { + const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studiothumbs.txt"; + + return ImageUtils.EnsureList(url, file, _httpClient, _fileSystem, _listResourcePool, cancellationToken); + } + + private Task EnsurePosterList(string file, CancellationToken cancellationToken) + { + const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studioposters.txt"; + + return ImageUtils.EnsureList(url, file, _httpClient, _fileSystem, _listResourcePool, cancellationToken); + } + + public int Priority + { + get { return 0; } + } + } +} diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 94d171ce14..b32670a91d 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -66,6 +66,9 @@ + + + @@ -120,8 +123,8 @@ - - + + @@ -167,12 +170,6 @@ - - - - - -