diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 298d26a13f..61259a4c25 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -699,11 +699,32 @@ namespace MediaBrowser.Api.Images var temp1 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp"); var temp2 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp"); + // Copying over will fail against hidden files + RemoveHiddenAttribute(file1); + RemoveHiddenAttribute(file2); + File.Copy(file1, temp1); File.Copy(file2, temp2); File.Copy(temp1, file2, true); File.Copy(temp2, file1, true); + + File.Delete(temp1); + File.Delete(temp2); + } + + private void RemoveHiddenAttribute(string path) + { + var currentFile = new FileInfo(path); + + // This will fail if the file is hidden + if (currentFile.Exists) + { + if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + currentFile.Attributes &= ~FileAttributes.Hidden; + } + } } /// @@ -854,7 +875,7 @@ namespace MediaBrowser.Api.Images // Handle image/png; charset=utf-8 mimeType = mimeType.Split(';').FirstOrDefault(); - await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, imageIndex, CancellationToken.None).ConfigureAwait(false); + await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, imageIndex, null, CancellationToken.None).ConfigureAwait(false); await entity.RefreshMetadata(CancellationToken.None, forceRefresh: true, forceSave: true, allowSlowProviders: false).ConfigureAwait(false); } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 3459c25976..6832ca7141 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -47,6 +47,7 @@ namespace MediaBrowser.Controller.Entities LockedFields = new List(); Taglines = new List(); RemoteTrailers = new List(); + ImageSources = new List(); } /// @@ -233,23 +234,6 @@ namespace MediaBrowser.Controller.Entities /// The locked fields. public List LockedFields { get; set; } - /// - /// Determines whether the item has a saved local image of the specified name (jpg or png). - /// - /// The name. - /// true if [has local image] [the specified item]; otherwise, false. - /// name - public bool HasLocalImage(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException("name"); - } - - return ResolveArgs.ContainsMetaFileByName(name + ".jpg") || - ResolveArgs.ContainsMetaFileByName(name + ".png"); - } - /// /// Should be overridden to return the proper folder where metadata lives /// @@ -536,6 +520,12 @@ namespace MediaBrowser.Controller.Entities /// The backdrop image paths. public List BackdropImagePaths { get; set; } + /// + /// Gets or sets the backdrop image sources. + /// + /// The backdrop image sources. + public List ImageSources { get; set; } + /// /// Gets or sets the screenshot image paths. /// @@ -1508,8 +1498,10 @@ namespace MediaBrowser.Controller.Entities BackdropImagePaths.Remove(file); + RemoveImageSourceForPath(file); + // Delete the source file - File.Delete(file); + DeleteImagePath(file); } else if (type == ImageType.Screenshot) { @@ -1523,12 +1515,12 @@ namespace MediaBrowser.Controller.Entities ScreenshotImagePaths.Remove(file); // Delete the source file - File.Delete(file); + DeleteImagePath(file); } else { // Delete the source file - File.Delete(GetImage(type)); + DeleteImagePath(GetImage(type)); // Remove it from the item SetImage(type, null); @@ -1538,6 +1530,26 @@ namespace MediaBrowser.Controller.Entities return RefreshMetadata(CancellationToken.None, forceSave: true); } + /// + /// Deletes the image path. + /// + /// The path. + private void DeleteImagePath(string path) + { + var currentFile = new FileInfo(path); + + // This will fail if the file is hidden + if (currentFile.Exists) + { + if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + currentFile.Attributes &= ~FileAttributes.Hidden; + } + + currentFile.Delete(); + } + } + /// /// Validates that images within the item are still on the file system /// @@ -1570,7 +1582,83 @@ namespace MediaBrowser.Controller.Entities foreach (var path in deletedImages) { BackdropImagePaths.Remove(path); + + RemoveImageSourceForPath(path); + } + } + + /// + /// Adds the image source. + /// + /// The path. + /// The URL. + public void AddImageSource(string path, string url) + { + RemoveImageSourceForPath(path); + + var pathMd5 = path.ToLower().GetMD5(); + + ImageSources.Add(new ImageSourceInfo + { + ImagePathMD5 = pathMd5, + ImageUrlMD5 = url.ToLower().GetMD5() + }); + } + + /// + /// Gets the image source info. + /// + /// The path. + /// ImageSourceInfo. + public ImageSourceInfo GetImageSourceInfo(string path) + { + if (ImageSources.Count == 0) + { + return null; + } + + var pathMd5 = path.ToLower().GetMD5(); + + return ImageSources.FirstOrDefault(i => i.ImagePathMD5 == pathMd5); + } + + /// + /// Removes the image source for path. + /// + /// The path. + public void RemoveImageSourceForPath(string path) + { + if (ImageSources.Count == 0) + { + return; } + + var pathMd5 = path.ToLower().GetMD5(); + + // Remove existing + foreach (var entry in ImageSources + .Where(i => i.ImagePathMD5 == pathMd5) + .ToList()) + { + ImageSources.Remove(entry); + } + } + + /// + /// Determines whether [contains image with source URL] [the specified URL]. + /// + /// The URL. + /// true if [contains image with source URL] [the specified URL]; otherwise, false. + public bool ContainsImageWithSourceUrl(string url) + { + if (ImageSources.Count == 0) + { + return false; + } + + var md5 = url.ToLower().GetMD5(); + + return ImageSources.Any(i => i.ImageUrlMD5 == md5); } /// diff --git a/MediaBrowser.Controller/Entities/ImageSourceInfo.cs b/MediaBrowser.Controller/Entities/ImageSourceInfo.cs new file mode 100644 index 0000000000..6dc072431c --- /dev/null +++ b/MediaBrowser.Controller/Entities/ImageSourceInfo.cs @@ -0,0 +1,10 @@ +using System; + +namespace MediaBrowser.Controller.Entities +{ + public class ImageSourceInfo + { + public Guid ImagePathMD5 { get; set; } + public Guid ImageUrlMD5 { get; set; } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5cde659ddf..8154cb0a28 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -91,6 +91,7 @@ + diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 46223efc33..6a4d132b76 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -43,9 +43,10 @@ namespace MediaBrowser.Controller.Providers /// Type of the MIME. /// The type. /// Index of the image. + /// The source URL. /// The cancellation token. /// Task. - Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken); + Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken); /// /// Adds the metadata providers. diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 9c830789d7..5f606a06bf 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -233,6 +233,12 @@ namespace MediaBrowser.Model.Configuration /// The width of the min movie backdrop. public int MinMovieBackdropWidth { get; set; } + /// + /// Gets or sets the width of the min series backdrop. + /// + /// The width of the min series backdrop. + public int MinSeriesBackdropWidth { get; set; } + /// /// Gets or sets the width of the min movie poster. /// @@ -286,6 +292,7 @@ namespace MediaBrowser.Model.Configuration SeasonZeroDisplayName = "Specials"; MinMovieBackdropWidth = 1920; + MinSeriesBackdropWidth = 1920; MinMoviePosterWidth = 1000; } } diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs index 9ef4c68887..f34988481e 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -153,7 +153,7 @@ namespace MediaBrowser.Providers.Movies item.HasImage(ImageType.Disc) && item.HasImage(ImageType.Banner) && item.HasImage(ImageType.Thumb) && - item.BackdropImagePaths.Count > 0) + item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) { return false; } @@ -389,7 +389,8 @@ namespace MediaBrowser.Providers.Movies } } - if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count == 0) + var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; + if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { var nodes = doc.SelectNodes("//fanart/movie/moviebackgrounds//@url"); @@ -401,14 +402,14 @@ namespace MediaBrowser.Providers.Movies { path = node.Value; - if (!string.IsNullOrEmpty(path)) + if (!string.IsNullOrEmpty(path) && !item.ContainsImageWithSourceUrl(path)) { await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) .ConfigureAwait(false); numBackdrops++; - if (item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) break; + if (item.BackdropImagePaths.Count >= backdropLimit) break; } } diff --git a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs index d03bc91ac9..b5d3443377 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs @@ -9,7 +9,6 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -135,7 +134,7 @@ namespace MediaBrowser.Providers.Movies } // Don't refresh if we already have both poster and backdrop and we're not refreshing images - if (item.HasImage(ImageType.Primary) && item.BackdropImagePaths.Count > 0) + if (item.HasImage(ImageType.Primary) && item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) { return false; } @@ -239,14 +238,16 @@ namespace MediaBrowser.Providers.Movies if (poster != null) { + var url = tmdbImageUrl + poster.file_path; + var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions { - Url = tmdbImageUrl + poster.file_path, + Url = url, CancellationToken = cancellationToken }).ConfigureAwait(false); - await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(poster.file_path), ImageType.Primary, null, cancellationToken) + await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(poster.file_path), ImageType.Primary, null, url, cancellationToken) .ConfigureAwait(false); } @@ -258,8 +259,10 @@ namespace MediaBrowser.Providers.Movies images.backdrops.Where(i => i.width >= ConfigurationManager.Configuration.MinMovieBackdropWidth) .ToList(); + var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; + // backdrops - only download if earlier providers didn't find any (fanart) - if (eligibleBackdrops.Count > 0 && ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count == 0) + if (eligibleBackdrops.Count > 0 && ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); @@ -267,24 +270,22 @@ namespace MediaBrowser.Providers.Movies for (var i = 0; i < eligibleBackdrops.Count; i++) { - var bdName = "backdrop" + (i == 0 ? "" : i.ToString(CultureInfo.InvariantCulture)); - - var hasLocalBackdrop = item.LocationType == LocationType.FileSystem && ConfigurationManager.Configuration.SaveLocalMeta ? item.HasLocalImage(bdName) : item.BackdropImagePaths.Count > i; + var url = tmdbImageUrl + eligibleBackdrops[i].file_path; - if (!hasLocalBackdrop) + if (!item.ContainsImageWithSourceUrl(url)) { var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions { - Url = tmdbImageUrl + eligibleBackdrops[i].file_path, + Url = url, CancellationToken = cancellationToken }).ConfigureAwait(false); - await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(eligibleBackdrops[i].file_path), ImageType.Backdrop, item.BackdropImagePaths.Count, cancellationToken) + await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(eligibleBackdrops[i].file_path), ImageType.Backdrop, item.BackdropImagePaths.Count, url, cancellationToken) .ConfigureAwait(false); } - if (item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) + if (item.BackdropImagePaths.Count >= backdropLimit) { break; } diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 46d6b0aa93..6d467fc6c9 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -513,7 +513,7 @@ namespace MediaBrowser.Providers.Movies { var isBoxSet = item is BoxSet; - var mainResult = await FetchMainResult(id, isBoxSet, cancellationToken).ConfigureAwait(false); + var mainResult = await FetchMainResult(id, isBoxSet, language, cancellationToken).ConfigureAwait(false); if (mainResult == null) return; @@ -550,7 +550,7 @@ namespace MediaBrowser.Providers.Movies { var language = ConfigurationManager.Configuration.PreferredMetadataLanguage; - var mainResult = await FetchMainResult(id, isBoxSet, cancellationToken).ConfigureAwait(false); + var mainResult = await FetchMainResult(id, isBoxSet, language, cancellationToken).ConfigureAwait(false); if (mainResult == null) return; @@ -588,13 +588,14 @@ namespace MediaBrowser.Providers.Movies /// /// The id. /// if set to true [is box set]. + /// The language. /// The cancellation token /// Task{CompleteMovieData}. - protected async Task FetchMainResult(string id, bool isBoxSet, CancellationToken cancellationToken) + protected async Task FetchMainResult(string id, bool isBoxSet, string language, CancellationToken cancellationToken) { var baseUrl = isBoxSet ? GetBoxSetInfo3 : GetMovieInfo3; - string url = string.Format(baseUrl, id, ApiKey, ConfigurationManager.Configuration.PreferredMetadataLanguage); + string url = string.Format(baseUrl, id, ApiKey, language); CompleteMovieData mainResult; cancellationToken.ThrowIfCancellationRequested(); @@ -614,13 +615,13 @@ namespace MediaBrowser.Providers.Movies if (mainResult != null && string.IsNullOrEmpty(mainResult.overview)) { - if (ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower() != "en") + if (language.ToLower() != "en") { - Logger.Info("MovieDbProvider couldn't find meta for language " + ConfigurationManager.Configuration.PreferredMetadataLanguage + ". Trying English..."); + Logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English..."); url = string.Format(baseUrl, id, ApiKey, "en"); - using (Stream json = await GetMovieDbResponse(new HttpRequestOptions + using (var json = await GetMovieDbResponse(new HttpRequestOptions { Url = url, CancellationToken = cancellationToken, diff --git a/MediaBrowser.Providers/Movies/TmdbPersonProvider.cs b/MediaBrowser.Providers/Movies/TmdbPersonProvider.cs index 3fc32c7051..685f4bc6e4 100644 --- a/MediaBrowser.Providers/Movies/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Movies/TmdbPersonProvider.cs @@ -390,7 +390,7 @@ namespace MediaBrowser.Providers.Movies }).ConfigureAwait(false)) { - await ProviderManager.SaveImage(item, sourceStream, mimeType, ImageType.Primary, null, cancellationToken) + await ProviderManager.SaveImage(item, sourceStream, mimeType, ImageType.Primary, null, source, cancellationToken) .ConfigureAwait(false); Logger.Debug("TmdbPersonProvider downloaded and saved image for {0}", item.Name); diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index fa7daaf251..e454b048c9 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 35bbba0422..79d53d5785 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -332,6 +332,8 @@ namespace MediaBrowser.Providers.Music } cancellationToken.ThrowIfCancellationRequested(); + var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; + if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops && item.BackdropImagePaths.Count == 0) { var nodes = doc.SelectNodes("//fanart/music/artistbackgrounds//@url"); @@ -342,14 +344,14 @@ namespace MediaBrowser.Providers.Music foreach (XmlNode node in nodes) { path = node.Value; - if (!string.IsNullOrEmpty(path)) + if (!string.IsNullOrEmpty(path) && !item.ContainsImageWithSourceUrl(path)) { try { await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) .ConfigureAwait(false); numBackdrops++; - if (numBackdrops >= ConfigurationManager.Configuration.MaxBackdrops) break; + if (numBackdrops >= backdropLimit) break; } catch (HttpException ex) { diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs index 5412266090..ed7ca941c2 100644 --- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Providers.TV item.HasImage(ImageType.Logo) && item.HasImage(ImageType.Banner) && item.HasImage(ImageType.Thumb) && - item.BackdropImagePaths.Count > 0) + item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) { return false; } @@ -298,7 +298,9 @@ namespace MediaBrowser.Providers.TV } } - if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count == 0) + var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; + + if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { var nodes = doc.SelectNodes("//fanart/series/showbackgrounds//@url"); @@ -310,14 +312,14 @@ namespace MediaBrowser.Providers.TV { var path = node.Value; - if (!string.IsNullOrEmpty(path)) + if (!string.IsNullOrEmpty(path) && !item.ContainsImageWithSourceUrl(path)) { await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) .ConfigureAwait(false); numBackdrops++; - if (item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) break; + if (item.BackdropImagePaths.Count >= backdropLimit) break; } } diff --git a/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs b/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs index 3436a137f5..c0ff760549 100644 --- a/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs @@ -121,6 +121,15 @@ namespace MediaBrowser.Providers.TV return false; } + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count > 0) + { + return false; + } + return base.NeedsRefreshInternal(item, providerInfo); + } + /// /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// @@ -146,7 +155,7 @@ namespace MediaBrowser.Providers.TV try { var fanartData = FetchFanartXmlData(imagesXmlPath, seasonNumber.Value, cancellationToken); - await DownloadImages(item, fanartData, ConfigurationManager.Configuration.MaxBackdrops, cancellationToken).ConfigureAwait(false); + await DownloadImages(item, fanartData, 1, cancellationToken).ConfigureAwait(false); } catch (FileNotFoundException) { @@ -186,13 +195,18 @@ namespace MediaBrowser.Providers.TV } } - if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count == 0) + if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { var bdNo = item.BackdropImagePaths.Count; foreach (var backdrop in data.Backdrops) { - var url = TVUtils.BannerUrl + backdrop; + var url = TVUtils.BannerUrl + backdrop.Url; + + if (item.ContainsImageWithSourceUrl(url)) + { + continue; + } await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken) .ConfigureAwait(false); @@ -260,6 +274,7 @@ namespace MediaBrowser.Providers.TV string bannerType2 = null; string url = null; int? bannerSeason = null; + string resolution = null; while (reader.Read()) { @@ -319,7 +334,11 @@ namespace MediaBrowser.Providers.TV } else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase)) { - data.Backdrops.Add(url); + data.Backdrops.Add(new ImageInfo + { + Url = url, + Resolution = resolution + }); } } } diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs index be97285c6c..fa42257607 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Net; +using System.Linq; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -133,6 +134,15 @@ namespace MediaBrowser.Providers.TV return base.CompareDate(item); } + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) + { + return false; + } + return base.NeedsRefreshInternal(item, providerInfo); + } + /// /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// @@ -154,10 +164,10 @@ namespace MediaBrowser.Providers.TV var imagesXmlPath = Path.Combine(seriesDataPath, "banners.xml"); - if (!series.HasImage(ImageType.Primary) || !series.HasImage(ImageType.Banner) || series.BackdropImagePaths.Count == 0) - { - var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; + var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops; + if (!series.HasImage(ImageType.Primary) || !series.HasImage(ImageType.Banner) || series.BackdropImagePaths.Count < backdropLimit) + { Directory.CreateDirectory(seriesDataPath); try @@ -171,13 +181,6 @@ namespace MediaBrowser.Providers.TV } } - BaseProviderInfo data; - if (!item.ProviderData.TryGetValue(Id, out data)) - { - data = new BaseProviderInfo(); - item.ProviderData[Id] = data; - } - SetLastRefreshed(item, DateTime.UtcNow); return true; } @@ -213,13 +216,39 @@ namespace MediaBrowser.Providers.TV } } - if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count == 0) + if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { var bdNo = item.BackdropImagePaths.Count; - foreach (var backdrop in data.Backdrops) + var eligibleBackdrops = data.Backdrops + .Where(i => + { + if (string.IsNullOrEmpty(i.Resolution)) + { + return true; + } + + var parts = i.Resolution.Split('x'); + + int width; + + if (int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width)) + { + return width >= ConfigurationManager.Configuration.MinSeriesBackdropWidth; + } + + return true; + }) + .ToList(); + + foreach (var backdrop in eligibleBackdrops) { - var url = TVUtils.BannerUrl + backdrop; + var url = TVUtils.BannerUrl + backdrop.Url; + + if (item.ContainsImageWithSourceUrl(url)) + { + continue; + } await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken) .ConfigureAwait(false); @@ -285,6 +314,7 @@ namespace MediaBrowser.Providers.TV string type = null; string url = null; + string resolution = null; while (reader.Read()) { @@ -312,13 +342,6 @@ namespace MediaBrowser.Providers.TV return; } } - else if (string.Equals(type, "fanart", StringComparison.OrdinalIgnoreCase)) - { - if (data.Backdrops.Count >= backdropLimit) - { - return; - } - } else { return; @@ -333,6 +356,12 @@ namespace MediaBrowser.Providers.TV break; } + case "BannerType2": + { + resolution = reader.ReadElementContentAsString() ?? string.Empty; + break; + } + default: reader.Skip(); break; @@ -352,7 +381,11 @@ namespace MediaBrowser.Providers.TV } else if (string.Equals(type, "fanart", StringComparison.OrdinalIgnoreCase)) { - data.Backdrops.Add(url); + data.Backdrops.Add(new ImageInfo + { + Url = url, + Resolution = resolution + }); } } } @@ -364,6 +397,12 @@ namespace MediaBrowser.Providers.TV public string LanguageBanner { get; set; } public string Poster { get; set; } public string Banner { get; set; } - public List Backdrops = new List(); + public List Backdrops = new List(); + } + + internal class ImageInfo + { + public string Url { get; set; } + public string Resolution { get; set; } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs index 7387b91662..51d31cd338 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -90,10 +90,22 @@ namespace MediaBrowser.Server.Implementations.Library.Validators MergeImages(musicArtist.Images, artist.Images); // Merge backdrops - var backdrops = musicArtist.BackdropImagePaths.ToList(); - backdrops.InsertRange(0, artist.BackdropImagePaths); - artist.BackdropImagePaths = backdrops.Distinct(StringComparer.OrdinalIgnoreCase) + var additionalBackdrops = musicArtist + .BackdropImagePaths + .Except(artist.BackdropImagePaths) .ToList(); + + var sources = additionalBackdrops + .Select(musicArtist.GetImageSourceInfo) + .ToList(); + + foreach (var path in additionalBackdrops) + { + artist.RemoveImageSourceForPath(path); + } + + artist.BackdropImagePaths.AddRange(additionalBackdrops); + artist.ImageSources.AddRange(sources); } if (!artist.LockedFields.Contains(MetadataFields.Genres)) diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index 18e64e414f..711b3f77ce 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -56,9 +56,11 @@ namespace MediaBrowser.Server.Implementations.Providers /// Type of the MIME. /// The type. /// Index of the image. + /// The source URL. /// The cancellation token. /// Task. - public async Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken) + /// mimeType + public async Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(mimeType)) { @@ -128,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Providers } // Set the path into the BaseItem - SetImagePath(item, type, imageIndex, paths[0]); + SetImagePath(item, type, imageIndex, paths[0], sourceUrl); // Delete the current path if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase)) @@ -137,7 +139,18 @@ namespace MediaBrowser.Server.Implementations.Providers try { - File.Delete(currentPath); + var currentFile = new FileInfo(currentPath); + + // This will fail if the file is hidden + if (currentFile.Exists) + { + if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + currentFile.Attributes &= ~FileAttributes.Hidden; + } + + currentFile.Delete(); + } } finally { @@ -244,12 +257,11 @@ namespace MediaBrowser.Server.Implementations.Providers /// The type. /// Index of the image. /// The path. - /// - /// imageIndex + /// The source URL. + /// imageIndex /// or - /// imageIndex - /// - private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path) + /// imageIndex + private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path, string sourceUrl) { switch (type) { @@ -282,6 +294,15 @@ namespace MediaBrowser.Server.Implementations.Providers { item.BackdropImagePaths.Add(path); } + + if (string.IsNullOrEmpty(sourceUrl)) + { + item.RemoveImageSourceForPath(path); + } + else + { + item.AddImageSource(path, sourceUrl); + } break; default: item.SetImage(type, path); diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index 8bd5012713..19230cecdb 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -325,7 +325,7 @@ namespace MediaBrowser.Server.Implementations.Providers }).ConfigureAwait(false); - await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken) + await SaveImage(item, response.Content, response.ContentType, type, imageIndex, url, cancellationToken) .ConfigureAwait(false); } @@ -337,11 +337,12 @@ namespace MediaBrowser.Server.Implementations.Providers /// Type of the MIME. /// The type. /// Index of the image. + /// The source URL. /// The cancellation token. /// Task. - public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken) + public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken) { - return new ImageSaver(ConfigurationManager, _directoryWatchers).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken); + return new ImageSaver(ConfigurationManager, _directoryWatchers).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 265cab1e71..473f2c67a7 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -174,6 +174,11 @@ namespace MediaBrowser.Server.Implementations.Session /// The item. private void RemoveNowPlayingItem(SessionInfo session, BaseItem item) { + if (item == null) + { + throw new ArgumentNullException("item"); + } + if (session.NowPlayingItem != null && session.NowPlayingItem.Id == item.Id) { session.NowPlayingItem = null; @@ -319,6 +324,16 @@ namespace MediaBrowser.Server.Implementations.Session throw new ArgumentNullException("info"); } + if (info.Item == null) + { + throw new ArgumentException("PlaybackStopInfo.Item cannot be null"); + } + + if (info.SessionId == Guid.Empty) + { + throw new ArgumentException("PlaybackStopInfo.SessionId cannot be Guid.Empty"); + } + if (info.PositionTicks.HasValue && info.PositionTicks.Value < 0) { throw new ArgumentOutOfRangeException("positionTicks");