diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs index c0992a7494..bb23f7f7e3 100644 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs @@ -471,6 +471,76 @@ namespace MediaBrowser.Api.DefaultTheme }).Where(i => i != null).ToList(); } + private void SetFavoriteGenres(MoviesView view, IEnumerable inputItems, User user) + { + var all = inputItems.SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase); + + view.FavoriteGenres = all.Select(i => + { + try + { + var itemByName = _libraryManager.GetGenre(i); + + var counts = itemByName.GetItemByNameCounts(user); + + var count = counts == null ? 0 : counts.MovieCount; + + if (count > 0 && _userDataManager.GetUserData(user.Id, itemByName.GetUserDataKey()).IsFavorite) + { + return new ItemByNameInfo + { + Name = itemByName.Name, + ItemCount = count + }; + } + } + catch (Exception ex) + { + _logger.ErrorException("Error getting genre {0}", ex, i); + + } + + return null; + + }).Where(i => i != null).ToList(); + } + + private void SetFavoriteStudios(MoviesView view, IEnumerable inputItems, User user) + { + var all = inputItems.SelectMany(i => i.Studios) + .Distinct(StringComparer.OrdinalIgnoreCase); + + view.FavoriteStudios = all.Select(i => + { + try + { + var itemByName = _libraryManager.GetStudio(i); + + var counts = itemByName.GetItemByNameCounts(user); + + var count = counts == null ? 0 : counts.MovieCount; + + if (count > 0 && _userDataManager.GetUserData(user.Id, itemByName.GetUserDataKey()).IsFavorite) + { + return new ItemByNameInfo + { + Name = itemByName.Name, + ItemCount = count + }; + } + } + catch (Exception ex) + { + _logger.ErrorException("Error getting studio {0}", ex, i); + + } + + return null; + + }).Where(i => i != null).ToList(); + } + public object Get(GetMovieView request) { var user = _userManager.GetUserById(request.UserId); @@ -487,6 +557,9 @@ namespace MediaBrowser.Api.DefaultTheme var movies = items.OfType() .ToList(); + SetFavoriteGenres(view, movies, user); + SetFavoriteStudios(view, movies, user); + var trailers = items.OfType() .ToList(); diff --git a/MediaBrowser.Api/DefaultTheme/Models.cs b/MediaBrowser.Api/DefaultTheme/Models.cs index 6dbd6b562d..bdff82de2a 100644 --- a/MediaBrowser.Api/DefaultTheme/Models.cs +++ b/MediaBrowser.Api/DefaultTheme/Models.cs @@ -34,6 +34,9 @@ namespace MediaBrowser.Api.DefaultTheme public List LatestTrailers { get; set; } public List LatestMovies { get; set; } + + public List FavoriteGenres { get; set; } + public List FavoriteStudios { get; set; } } public class TvView : BaseView diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 61259a4c25..0e5f420b53 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -861,21 +861,10 @@ namespace MediaBrowser.Api.Images Position = 0 }; - var imageIndex = 0; - - if (imageType == ImageType.Screenshot) - { - imageIndex = entity.ScreenshotImagePaths.Count; - } - else if (imageType == ImageType.Backdrop) - { - imageIndex = entity.BackdropImagePaths.Count; - } - // Handle image/png; charset=utf-8 mimeType = mimeType.Split(';').FirstOrDefault(); - await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, imageIndex, null, CancellationToken.None).ConfigureAwait(false); + await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, null, CancellationToken.None).ConfigureAwait(false); await entity.RefreshMetadata(CancellationToken.None, forceRefresh: true, forceSave: true, allowSlowProviders: false).ConfigureAwait(false); } diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index 0cf51abac8..f92d9d581b 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -282,14 +282,7 @@ namespace MediaBrowser.Api.Images /// Task. private async Task DownloadRemoteImage(BaseItem item, BaseDownloadRemoteImage request) { - int? index = null; - - if (request.Type == ImageType.Backdrop) - { - index = item.BackdropImagePaths.Count; - } - - await _providerManager.SaveImage(item, request.ImageUrl, null, request.Type, index, CancellationToken.None).ConfigureAwait(false); + await _providerManager.SaveImage(item, request.ImageUrl, null, request.Type, null, CancellationToken.None).ConfigureAwait(false); await item.RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false) .ConfigureAwait(false); diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index ace8c4a6e0..6e1dbc08b9 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -219,8 +219,12 @@ namespace MediaBrowser.Api item.Budget = request.Budget; item.Revenue = request.Revenue; - item.CriticRating = request.CriticRating; - item.CriticRatingSummary = request.CriticRatingSummary; + var hasCriticRating = item as IHasCriticRating; + if (hasCriticRating != null) + { + hasCriticRating.CriticRating = request.CriticRating; + hasCriticRating.CriticRatingSummary = request.CriticRatingSummary; + } item.DisplayMediaType = request.DisplayMediaType; item.CommunityRating = request.CommunityRating; diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 29a894b1c8..9547cc0a4e 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -581,7 +581,17 @@ namespace MediaBrowser.Api.UserLibrary { var val = request.MinCriticRating.Value; - items = items.Where(i => i.CriticRating.HasValue && i.CriticRating >= val); + items = items.Where(i => + { + var hasCriticRating = i as IHasCriticRating; + + if (hasCriticRating != null) + { + return hasCriticRating.CriticRating.HasValue && hasCriticRating.CriticRating >= val; + } + + return false; + }); } // Artists diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 76a079c577..9222a89079 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -40,5 +40,37 @@ namespace MediaBrowser.Controller.Drawing public int? PercentPlayed { get; set; } public string BackgroundColor { get; set; } + + public bool HasDefaultOptions() + { + return HasDefaultOptionsWithoutSize() && + !Width.HasValue && + !Height.HasValue && + !MaxWidth.HasValue && + !MaxHeight.HasValue; + } + + public bool HasDefaultOptionsWithoutSize() + { + return !CropWhiteSpace && + (!Quality.HasValue || Quality.Value == 100) && + IsOutputFormatDefault && + !AddPlayedIndicator && + !PercentPlayed.HasValue && + string.IsNullOrEmpty(BackgroundColor); + } + + private bool IsOutputFormatDefault + { + get + { + if (OutputFormat == ImageOutputFormat.Original) + { + return true; + } + + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/Artist.cs b/MediaBrowser.Controller/Entities/Audio/Artist.cs index 5e14b01529..947ee11227 100644 --- a/MediaBrowser.Controller/Entities/Audio/Artist.cs +++ b/MediaBrowser.Controller/Entities/Audio/Artist.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.Controller.Entities.Audio } public string LastFmImageUrl { get; set; } + public string LastFmImageSize { get; set; } /// /// Gets the user data key. diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index b96cd625fe..cb64cfdfef 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -17,6 +17,7 @@ namespace MediaBrowser.Controller.Entities.Audio } public string LastFmImageUrl { get; set; } + public string LastFmImageSize { get; set; } /// /// Songs will group into us so don't also include us in the index diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 75231ec6aa..d791c92ae9 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -11,6 +11,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// The last fm image URL. public string LastFmImageUrl { get; set; } + public string LastFmImageSize { get; set; } /// /// Gets the user data key. diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 839fe34ffe..f8b2fad230 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -111,18 +111,6 @@ namespace MediaBrowser.Controller.Entities /// The revenue. public double? Revenue { get; set; } - /// - /// Gets or sets the critic rating. - /// - /// The critic rating. - public float? CriticRating { get; set; } - - /// - /// Gets or sets the critic rating summary. - /// - /// The critic rating summary. - public string CriticRatingSummary { get; set; } - /// /// Gets or sets the trailer URL. /// diff --git a/MediaBrowser.Controller/Entities/IHasCriticRating.cs b/MediaBrowser.Controller/Entities/IHasCriticRating.cs new file mode 100644 index 0000000000..d2b93759d8 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasCriticRating.cs @@ -0,0 +1,20 @@ +namespace MediaBrowser.Controller.Entities +{ + /// + /// Interface IHasCriticRating + /// + public interface IHasCriticRating + { + /// + /// Gets or sets the critic rating. + /// + /// The critic rating. + float? CriticRating { get; set; } + + /// + /// Gets or sets the critic rating summary. + /// + /// The critic rating summary. + string CriticRatingSummary { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 516ee9a8ae..54ad9c1c97 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Entities.Movies /// /// Class Movie /// - public class Movie : Video + public class Movie : Video, IHasCriticRating { public List SpecialFeatureIds { get; set; } @@ -20,6 +20,18 @@ namespace MediaBrowser.Controller.Entities.Movies SpecialFeatureIds = new List(); } + /// + /// Gets or sets the critic rating. + /// + /// The critic rating. + public float? CriticRating { get; set; } + + /// + /// Gets or sets the critic rating summary. + /// + /// The critic rating summary. + public string CriticRatingSummary { get; set; } + /// /// Gets or sets the name of the TMDB collection. /// diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index e7d47f7ec7..c9fe471b3c 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Entities /// /// Class Trailer /// - public class Trailer : Video + public class Trailer : Video, IHasCriticRating { public Trailer() { @@ -15,6 +15,18 @@ namespace MediaBrowser.Controller.Entities Taglines = new List(); } + /// + /// Gets or sets the critic rating. + /// + /// The critic rating. + public float? CriticRating { get; set; } + + /// + /// Gets or sets the critic rating summary. + /// + /// The critic rating summary. + public string CriticRatingSummary { get; set; } + /// /// Gets a value indicating whether this instance is local trailer. /// diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 978d56bd4e..94ff5d3054 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -89,6 +89,7 @@ + diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index e9bb7f66d9..9fdbbf3b7e 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -143,10 +143,16 @@ namespace MediaBrowser.Controller.Providers case "CriticRating": { var text = reader.ReadElementContentAsString(); - float value; - if (float.TryParse(text, NumberStyles.Any, _usCulture, out value)) + + var hasCriticRating = item as IHasCriticRating; + + if (hasCriticRating != null && !string.IsNullOrEmpty(text)) { - item.CriticRating = value; + float value; + if (float.TryParse(text, NumberStyles.Any, _usCulture, out value)) + { + hasCriticRating.CriticRating = value; + } } break; @@ -207,7 +213,12 @@ namespace MediaBrowser.Controller.Providers if (!string.IsNullOrWhiteSpace(val)) { - item.CriticRatingSummary = val; + var hasCriticRating = item as IHasCriticRating; + + if (hasCriticRating != null) + { + hasCriticRating.CriticRatingSummary = val; + } } break; diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index de7e8e98d9..07bb7d5b20 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -24,6 +24,7 @@ namespace MediaBrowser.Controller.Providers /// /// The logger. protected ILogger Logger { get; set; } + protected ILogManager LogManager { get; set; } /// @@ -41,6 +42,7 @@ namespace MediaBrowser.Controller.Providers /// The true task result /// protected static readonly Task TrueTaskResult = Task.FromResult(true); + protected static readonly Task FalseTaskResult = Task.FromResult(false); protected static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(5, 5); @@ -132,7 +134,8 @@ namespace MediaBrowser.Controller.Providers /// The provider version. /// The status. /// item - public virtual void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion, ProviderRefreshStatus status = ProviderRefreshStatus.Success) + public virtual void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion, + ProviderRefreshStatus status = ProviderRefreshStatus.Success) { if (item == null) { @@ -172,7 +175,8 @@ namespace MediaBrowser.Controller.Providers /// The item. /// The value. /// The status. - public void SetLastRefreshed(BaseItem item, DateTime value, ProviderRefreshStatus status = ProviderRefreshStatus.Success) + public void SetLastRefreshed(BaseItem item, DateTime value, + ProviderRefreshStatus status = ProviderRefreshStatus.Success) { SetLastRefreshed(item, value, ProviderVersion, status); } @@ -238,7 +242,8 @@ namespace MediaBrowser.Controller.Providers return true; } - if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem && HasFileSystemStampChanged(item, providerInfo)) + if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem && + HasFileSystemStampChanged(item, providerInfo)) { return true; } @@ -349,21 +354,23 @@ namespace MediaBrowser.Controller.Providers } private Dictionary _fileStampExtensionsDictionary; + private Dictionary FileStampExtensionsDictionary { get { return _fileStampExtensionsDictionary ?? (_fileStampExtensionsDictionary = - FilestampExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase)); + FilestampExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase)); } } + /// /// Gets the file system stamp. /// /// The item. /// Guid. - private Guid GetFileSystemStamp(BaseItem item) + protected virtual Guid GetFileSystemStamp(BaseItem item) { // If there's no path or the item is a file, there's nothing to do if (item.LocationType != LocationType.FileSystem) @@ -404,6 +411,20 @@ namespace MediaBrowser.Controller.Providers private static readonly Dictionary FoldersToMonitor = new[] { "extrafanart", "extrathumbs" } .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); + protected Guid GetFileSystemStamp(IEnumerable files) + { + var sb = new StringBuilder(); + + var extensions = FileStampExtensionsDictionary; + var numExtensions = FilestampExtensions.Length; + + // Record the name of each file + // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order + AddFiles(sb, files, extensions, numExtensions); + + return sb.ToString().GetMD5(); + } + /// /// Adds the files. /// @@ -424,7 +445,7 @@ namespace MediaBrowser.Controller.Providers { sb.Append(file.Name); - var children = ((DirectoryInfo) file).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList(); + var children = ((DirectoryInfo)file).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList(); AddFiles(sb, children, extensions, numExtensions); } } diff --git a/MediaBrowser.Model/Drawing/DrawingUtils.cs b/MediaBrowser.Model/Drawing/DrawingUtils.cs index fabe1b24c3..e95b5e3752 100644 --- a/MediaBrowser.Model/Drawing/DrawingUtils.cs +++ b/MediaBrowser.Model/Drawing/DrawingUtils.cs @@ -1,4 +1,5 @@ - +using System.Globalization; + namespace MediaBrowser.Model.Drawing { /// @@ -131,15 +132,77 @@ namespace MediaBrowser.Model.Drawing /// public struct ImageSize { + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + private double _height; + private double _width; + /// /// Gets or sets the height. /// /// The height. - public double Height { get; set; } + public double Height + { + get + { + return _height; + } + set + { + _height = value; + } + } + /// /// Gets or sets the width. /// /// The width. - public double Width { get; set; } + public double Width + { + get { return _width; } + set { _width = value; } + } + + public bool Equals(ImageSize size) + { + return Width.Equals(size.Width) && Height.Equals(size.Height); + } + + public override string ToString() + { + return string.Format("{0}-{1}", Width, Height); + } + + public ImageSize(string value) + { + _width = 0; + + _height = 0; + + ParseValue(value); + } + + private void ParseValue(string value) + { + if (!string.IsNullOrEmpty(value)) + { + var parts = value.Split('-'); + + if (parts.Length == 2) + { + double val; + + if (double.TryParse(parts[0], NumberStyles.Any, UsCulture, out val)) + { + _width = val; + } + + if (double.TryParse(parts[1], NumberStyles.Any, UsCulture, out val)) + { + _height = val; + } + } + } + } } } diff --git a/MediaBrowser.Providers/CollectionFolderImageProvider.cs b/MediaBrowser.Providers/CollectionFolderImageProvider.cs new file mode 100644 index 0000000000..45b1b36c26 --- /dev/null +++ b/MediaBrowser.Providers/CollectionFolderImageProvider.cs @@ -0,0 +1,55 @@ +using MediaBrowser.Common.IO; +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 System; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Providers +{ + public class CollectionFolderImageProvider : ImageFromMediaLocationProvider + { + public CollectionFolderImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) + : base(logManager, configurationManager, fileSystem) + { + } + + public override bool Supports(BaseItem item) + { + return item is CollectionFolder && item.LocationType == LocationType.FileSystem; + } + + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Second; } + } + + protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension) + { + return item.ResolveArgs.PhysicalLocations + .Select(i => GetImageFromLocation(i, filenameWithoutExtension)) + .FirstOrDefault(i => i != null); + } + + protected override Guid GetFileSystemStamp(BaseItem item) + { + var files = item.ResolveArgs.PhysicalLocations + .Select(i => new DirectoryInfo(i)) + .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)) + .Where(i => + { + var ext = i.Extension; + + return !string.IsNullOrEmpty(ext) && + BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + }) + .ToList(); + + return GetFileSystemStamp(files); + } + } +} diff --git a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs index 9943c7ab8a..aa0dcc9d47 100644 --- a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -21,9 +22,12 @@ namespace MediaBrowser.Providers /// public class ImageFromMediaLocationProvider : BaseMetadataProvider { - public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager) + protected readonly IFileSystem FileSystem; + + public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) : base(logManager, configurationManager) { + FileSystem = fileSystem; } public override ItemUpdateType ItemUpdateType @@ -543,5 +547,37 @@ namespace MediaBrowser.Providers item.ScreenshotImagePaths = screenshotFiles; } } + + protected FileSystemInfo GetImageFromLocation(string path, string filenameWithoutExtension) + { + try + { + var files = new DirectoryInfo(path) + .EnumerateFiles() + .Where(i => + { + var fileName = Path.GetFileNameWithoutExtension(i.FullName); + + if (!string.Equals(fileName, filenameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var ext = i.Extension; + + return !string.IsNullOrEmpty(ext) && + BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + }) + .ToList(); + + return BaseItem.SupportedImageExtensions + .Select(ext => files.FirstOrDefault(i => string.Equals(ext, i.Extension, StringComparison.OrdinalIgnoreCase))) + .FirstOrDefault(file => file != null); + } + catch (DirectoryNotFoundException) + { + return null; + } + } } } diff --git a/MediaBrowser.Providers/ImagesByNameProvider.cs b/MediaBrowser.Providers/ImagesByNameProvider.cs index 6be4ee108b..5904308234 100644 --- a/MediaBrowser.Providers/ImagesByNameProvider.cs +++ b/MediaBrowser.Providers/ImagesByNameProvider.cs @@ -1,16 +1,12 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers { @@ -19,22 +15,11 @@ namespace MediaBrowser.Providers /// public class ImagesByNameProvider : ImageFromMediaLocationProvider { - private readonly IFileSystem _fileSystem; - public ImagesByNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - : base(logManager, configurationManager) + : base(logManager, configurationManager, fileSystem) { - _fileSystem = fileSystem; } - public override ItemUpdateType ItemUpdateType - { - get - { - return ItemUpdateType.ImageUpdate; - } - } - /// /// Supportses the specified item. /// @@ -42,8 +27,8 @@ namespace MediaBrowser.Providers /// true if XXXX, false otherwise public override bool Supports(BaseItem item) { - //only run for these generic types since we are expensive in file i/o - return item is IndexFolder || item is BasePluginFolder || item is CollectionFolder; + // Only run for these generic types since we are expensive in file i/o + return item is BasePluginFolder || item is CollectionFolder; } /// @@ -58,95 +43,6 @@ namespace MediaBrowser.Providers } } - /// - /// Needses the refresh internal. - /// - /// The item. - /// The provider info. - /// true if XXXX, false otherwise - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - // Force a refresh if the IBN path changed - if (providerInfo.FileStamp != ConfigurationManager.ApplicationPaths.ItemsByNamePath.GetMD5()) - { - return true; - } - - return base.NeedsRefreshInternal(item, providerInfo); - } - - /// - /// Gets a value indicating whether [refresh on file system stamp change]. - /// - /// true if [refresh on file system stamp change]; otherwise, false. - protected override bool RefreshOnFileSystemStampChange - { - get - { - return false; - } - } - - /// - /// Override this to return the date that should be compared to the last refresh date - /// to determine if this provider should be re-fetched. - /// - /// The item. - /// DateTime. - protected override DateTime CompareDate(BaseItem item) - { - // If the IBN location exists return the last modified date of any file in it - var location = GetLocation(item); - - var directoryInfo = new DirectoryInfo(location); - - if (!directoryInfo.Exists) - { - return DateTime.MinValue; - } - - var files = directoryInfo.EnumerateFiles().ToList(); - - if (files.Count == 0) - { - return DateTime.MinValue; - } - - return files.Select(f => - { - var lastWriteTime = _fileSystem.GetLastWriteTimeUtc(f); - var creationTime = _fileSystem.GetCreationTimeUtc(f); - - return creationTime > lastWriteTime ? creationTime : lastWriteTime; - - }).Max(); - } - - /// - /// 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 result = await base.FetchAsync(item, force, cancellationToken).ConfigureAwait(false); - - BaseProviderInfo data; - - if (!item.ProviderData.TryGetValue(Id, out data)) - { - data = new BaseProviderInfo(); - item.ProviderData[Id] = data; - } - - data.FileStamp = ConfigurationManager.ApplicationPaths.ItemsByNamePath.GetMD5(); - SetLastRefreshed(item, DateTime.UtcNow); - - return result; - } - /// /// Gets the location. /// @@ -154,7 +50,7 @@ namespace MediaBrowser.Providers /// System.String. protected string GetLocation(BaseItem item) { - var name = _fileSystem.GetValidFilename(item.Name); + var name = FileSystem.GetValidFilename(item.Name); return Path.Combine(ConfigurationManager.ApplicationPaths.GeneralPath, name); } @@ -170,30 +66,34 @@ namespace MediaBrowser.Providers { var location = GetLocation(item); - var directoryInfo = new DirectoryInfo(location); - - if (!directoryInfo.Exists) - { - return null; - } - - var files = directoryInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList(); + return GetImageFromLocation(location, filenameWithoutExtension); + } - var file = files.FirstOrDefault(i => string.Equals(i.Name, filenameWithoutExtension + ".png", StringComparison.OrdinalIgnoreCase)); + protected override Guid GetFileSystemStamp(BaseItem item) + { + var location = GetLocation(item); - if (file != null) + try { - return file; + var files = new DirectoryInfo(location) + .EnumerateFiles("*", SearchOption.TopDirectoryOnly) + .Where(i => + { + var ext = i.Extension; + + return !string.IsNullOrEmpty(ext) && + BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + }) + .ToList(); + + return GetFileSystemStamp(files); } - - file = files.FirstOrDefault(i => string.Equals(i.Name, filenameWithoutExtension + ".jpg", StringComparison.OrdinalIgnoreCase)); - - if (file != null) + catch (DirectoryNotFoundException) { - return file; - } + // User doesn't have the folder. No need to log or blow up - return null; + return Guid.Empty; + } } } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index f4d0ad7eb0..ad5dd9b3fd 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -47,6 +47,7 @@ + @@ -92,6 +93,7 @@ + diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs index 323fa20e6c..f65bf9ea3a 100644 --- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs @@ -251,17 +251,33 @@ namespace MediaBrowser.Providers.MediaInfo private string GetAspectRatio(MediaStreamInfo info) { - if (info.height > 0 && info.width > 0) + var original = info.display_aspect_ratio; + + int height; + int width; + + var parts = (original ?? string.Empty).Split(':'); + if (!(parts.Length == 2 && + int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width) && + int.TryParse(parts[1], NumberStyles.Any, UsCulture, out height) && + width > 0 && + height > 0)) + { + width = info.width; + height = info.height; + } + + if (width > 0 && height > 0) { - double ratio = info.width; - ratio /= info.height; + double ratio = width; + ratio /= height; - if (IsClose(ratio, 1.777777778)) + if (IsClose(ratio, 1.777777778, .03)) { return "16:9"; } - if (IsClose(ratio, 1.3333333333)) + if (IsClose(ratio, 1.3333333333, .05)) { return "4:3"; } @@ -286,31 +302,30 @@ namespace MediaBrowser.Providers.MediaInfo return "5:3"; } - if (IsClose(ratio, 1.85)) + if (IsClose(ratio, 1.85, .02)) { return "1.85:1"; } - if (IsClose(ratio, 2.39)) + if (IsClose(ratio, 2.35, .025)) { - return "2.39:1"; + return "2.35:1"; } - if (IsClose(ratio, 2.4)) + if (IsClose(ratio, 2.4, .025)) { - return "2.4:1"; + return "2.40:1"; } } - return info.display_aspect_ratio; + return original; } - private bool IsClose(double d1, double d2) + private bool IsClose(double d1, double d2, double variance = .005) { - return Math.Abs(d1 - d2) <= .005; + return Math.Abs(d1 - d2) <= variance; } - /// /// Gets a frame rate from a string value in ffprobe output /// This could be a number or in the format of 2997/125. diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs index 24f17556b4..39908c3eef 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -327,15 +327,11 @@ namespace MediaBrowser.Providers.Movies if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { - var numBackdrops = item.BackdropImagePaths.Count; - foreach (var image in images.Where(i => i.Type == ImageType.Backdrop)) { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken) .ConfigureAwait(false); - numBackdrops++; - if (item.BackdropImagePaths.Count >= backdropLimit) break; } } diff --git a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs index ab6dbf6d4d..d2a12ac213 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs @@ -231,7 +231,7 @@ namespace MediaBrowser.Providers.Movies }).ConfigureAwait(false); - await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Backdrop, item.BackdropImagePaths.Count, url, cancellationToken) + await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Backdrop, null, url, cancellationToken) .ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 9d71fef0ad..cc6e07d622 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -822,13 +822,18 @@ namespace MediaBrowser.Providers.Movies // genres // Movies get this from imdb - if (movieData.genres != null && !movie.LockedFields.Contains(MetadataFields.Genres) && movie is BoxSet) + if (movieData.genres != null && !movie.LockedFields.Contains(MetadataFields.Genres)) { - movie.Genres.Clear(); - - foreach (var genre in movieData.genres.Select(g => g.name)) + // Only grab them if a boxset or there are no genres. + // For movies and trailers we'll use imdb via omdb + if (movie is BoxSet || movie.Genres.Count == 0) { - movie.AddGenre(genre); + movie.Genres.Clear(); + + foreach (var genre in movieData.genres.Select(g => g.name)) + { + movie.AddGenre(genre); + } } } diff --git a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs index a6fdbdcef7..6550396e51 100644 --- a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs +++ b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs @@ -139,22 +139,26 @@ namespace MediaBrowser.Providers.Movies { var result = JsonSerializer.DeserializeFromStream(stream); - // Seeing some bogus RT data on omdb for series, so filter it out here - // RT doesn't even have tv series - int tomatoMeter; - - if (!string.IsNullOrEmpty(result.tomatoMeter) - && int.TryParse(result.tomatoMeter, NumberStyles.Integer, UsCulture, out tomatoMeter) - && tomatoMeter >= 0) - { - item.CriticRating = tomatoMeter; - } - - if (!string.IsNullOrEmpty(result.tomatoConsensus) - && !string.Equals(result.tomatoConsensus, "n/a", StringComparison.OrdinalIgnoreCase) - && !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase)) + var hasCriticRating = item as IHasCriticRating; + if (hasCriticRating != null) { - item.CriticRatingSummary = result.tomatoConsensus; + // Seeing some bogus RT data on omdb for series, so filter it out here + // RT doesn't even have tv series + int tomatoMeter; + + if (!string.IsNullOrEmpty(result.tomatoMeter) + && int.TryParse(result.tomatoMeter, NumberStyles.Integer, UsCulture, out tomatoMeter) + && tomatoMeter >= 0) + { + hasCriticRating.CriticRating = tomatoMeter; + } + + if (!string.IsNullOrEmpty(result.tomatoConsensus) + && !string.Equals(result.tomatoConsensus, "n/a", StringComparison.OrdinalIgnoreCase) + && !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase)) + { + hasCriticRating.CriticRatingSummary = result.tomatoConsensus; + } } int voteCount; diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 51adbc785c..6df21b61d9 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -8,17 +8,14 @@ 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; -using System.Net; using System.Threading; using System.Threading.Tasks; -using System.Xml; namespace MediaBrowser.Providers.Music { @@ -323,15 +320,11 @@ namespace MediaBrowser.Providers.Music if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { - var numBackdrops = item.BackdropImagePaths.Count; - foreach (var image in images.Where(i => i.Type == ImageType.Backdrop)) { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken) .ConfigureAwait(false); - numBackdrops++; - if (item.BackdropImagePaths.Count >= backdropLimit) break; } } diff --git a/MediaBrowser.Providers/Music/LastFmImageProvider.cs b/MediaBrowser.Providers/Music/LastFmImageProvider.cs index 5078326af9..cd40052235 100644 --- a/MediaBrowser.Providers/Music/LastFmImageProvider.cs +++ b/MediaBrowser.Providers/Music/LastFmImageProvider.cs @@ -4,7 +4,10 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Providers; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -55,11 +58,6 @@ namespace MediaBrowser.Providers.Music return false; } - if (string.IsNullOrWhiteSpace(GetImageUrl(item))) - { - return false; - } - return base.NeedsRefreshInternal(item, providerInfo); } @@ -72,56 +70,44 @@ namespace MediaBrowser.Providers.Music /// Task{System.Boolean}. public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - var url = GetImageUrl(item); - - if (!string.IsNullOrWhiteSpace(url) && !item.HasImage(ImageType.Primary)) + if (!item.HasImage(ImageType.Primary)) { - await _providerManager.SaveImage(item, url, LastfmBaseProvider.LastfmResourcePool, ImageType.Primary, null, cancellationToken) - .ConfigureAwait(false); + var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualLastFmImageProvider.ProviderName).ConfigureAwait(false); + + await DownloadImages(item, images.ToList(), cancellationToken).ConfigureAwait(false); } SetLastRefreshed(item, DateTime.UtcNow); + return true; } - /// - /// Gets the priority. - /// - /// The priority. - public override MetadataProviderPriority Priority + private async Task DownloadImages(BaseItem item, List images, CancellationToken cancellationToken) { - get { return MetadataProviderPriority.Fifth; } - } + cancellationToken.ThrowIfCancellationRequested(); - /// - /// 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 configSetting = item is MusicAlbum + ? ConfigurationManager.Configuration.DownloadMusicAlbumImages + : ConfigurationManager.Configuration.DownloadMusicArtistImages; - var artistByName = item as Artist; - - if (artistByName != null) + if (configSetting.Primary && !item.HasImage(ImageType.Primary)) { - return artistByName.LastFmImageUrl; - } + var image = images.FirstOrDefault(i => i.Type == ImageType.Primary); - var album = item as MusicAlbum; - - if (album != null) - { - return album.LastFmImageUrl; + if (image != null) + { + await _providerManager.SaveImage(item, image.Url, LastfmBaseProvider.LastfmResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); + } } + } - return null; + /// + /// Gets the priority. + /// + /// The priority. + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Fifth; } } } } diff --git a/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs b/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs index 7192b5949b..d0e807a862 100644 --- a/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs @@ -46,7 +46,7 @@ namespace MediaBrowser.Providers.Music { get { - return "8"; + return "9"; } } diff --git a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs index 7d83c3f6f2..146992039a 100644 --- a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Providers.Music { get { - return "8"; + return "9"; } } diff --git a/MediaBrowser.Providers/Music/LastfmHelper.cs b/MediaBrowser.Providers/Music/LastfmHelper.cs index ae849b4189..f529fd44a1 100644 --- a/MediaBrowser.Providers/Music/LastfmHelper.cs +++ b/MediaBrowser.Providers/Music/LastfmHelper.cs @@ -34,21 +34,27 @@ namespace MediaBrowser.Providers.Music var musicArtist = artist as MusicArtist; + string imageSize; + if (musicArtist != null) { - musicArtist.LastFmImageUrl = GetImageUrl(data); + musicArtist.LastFmImageUrl = GetImageUrl(data, out imageSize); + musicArtist.LastFmImageSize = imageSize; } var artistByName = artist as Artist; if (artistByName != null) { - artistByName.LastFmImageUrl = GetImageUrl(data); + artistByName.LastFmImageUrl = GetImageUrl(data, out imageSize); + artistByName.LastFmImageSize = imageSize; } } - private static string GetImageUrl(IHasLastFmImages data) + private static string GetImageUrl(IHasLastFmImages data, out string size) { + size = null; + if (data.image == null) { return null; @@ -61,12 +67,13 @@ namespace MediaBrowser.Providers.Music var img = validImages .FirstOrDefault(i => string.Equals(i.size, "mega", StringComparison.OrdinalIgnoreCase)) ?? 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(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) { + size = img.size; return img.url; } @@ -81,7 +88,7 @@ namespace MediaBrowser.Providers.Music target.Overview = source.Overview; target.ProductionLocations = source.ProductionLocations.ToList(); } - + public static void ProcessAlbumData(BaseItem item, LastfmAlbum data) { if (!string.IsNullOrWhiteSpace(data.mbid)) item.SetProviderId(MetadataProviders.Musicbrainz, data.mbid); @@ -107,7 +114,11 @@ namespace MediaBrowser.Providers.Music } var album = (MusicAlbum)item; - album.LastFmImageUrl = GetImageUrl(data); + + string imageSize; + + album.LastFmImageUrl = GetImageUrl(data, out imageSize); + album.LastFmImageSize = imageSize; } private static void AddTags(BaseItem item, LastfmTags tags) diff --git a/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs b/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs index bd79da15cc..d95365b02d 100644 --- a/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs @@ -325,7 +325,7 @@ namespace MediaBrowser.Providers.Music public int Priority { - get { return 0; } + get { return 1; } } } } diff --git a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs index 2e4dbe8136..6ff891f608 100644 --- a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs +++ b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs @@ -334,7 +334,7 @@ namespace MediaBrowser.Providers.Music public int Priority { - get { return 0; } + get { return 1; } } } } diff --git a/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs new file mode 100644 index 0000000000..0b1b95b1e5 --- /dev/null +++ b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs @@ -0,0 +1,110 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + public class ManualLastFmImageProvider : IImageProvider + { + public string Name + { + get { return ProviderName; } + } + + public static string ProviderName + { + get { return "last.fm"; } + } + + public bool Supports(BaseItem item) + { + return item is MusicAlbum || 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(); + + RemoteImageInfo info = null; + + var artist = item as Artist; + + if (artist != null) + { + info = GetInfo(artist.LastFmImageUrl, artist.LastFmImageSize); + } + + var album = item as MusicAlbum; + if (album != null) + { + info = GetInfo(album.LastFmImageUrl, album.LastFmImageSize); + } + + var musicArtist = item as MusicArtist; + if (musicArtist != null) + { + info = GetInfo(musicArtist.LastFmImageUrl, musicArtist.LastFmImageSize); + } + + if (info != null) + { + list.Add(info); + } + + // The only info we have is size + return Task.FromResult>(list.OrderByDescending(i => i.Width ?? 0)); + } + + private RemoteImageInfo GetInfo(string url, string size) + { + if (string.IsNullOrEmpty(url)) + { + return null; + } + + var info = new RemoteImageInfo + { + ProviderName = Name, + Url = url + }; + + if (string.Equals(size, "mega", StringComparison.OrdinalIgnoreCase)) + { + + } + else if (string.Equals(size, "extralarge", StringComparison.OrdinalIgnoreCase)) + { + + } + else if (string.Equals(size, "large", StringComparison.OrdinalIgnoreCase)) + { + + } + else if (string.Equals(size, "medium", StringComparison.OrdinalIgnoreCase)) + { + + } + + return info; + } + + public int Priority + { + get { return 0; } + } + } +} diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index d9e0fb6e2f..69276e0b8e 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -209,14 +209,18 @@ namespace MediaBrowser.Providers.Savers builder.Append("" + SecurityElement.Escape(item.DisplayMediaType) + ""); } - if (item.CriticRating.HasValue) + var hasCriticRating = item as IHasCriticRating; + if (hasCriticRating != null) { - builder.Append("" + SecurityElement.Escape(item.CriticRating.Value.ToString(UsCulture)) + ""); - } + if (hasCriticRating.CriticRating.HasValue) + { + builder.Append("" + SecurityElement.Escape(hasCriticRating.CriticRating.Value.ToString(UsCulture)) + ""); + } - if (!string.IsNullOrEmpty(item.CriticRatingSummary)) - { - builder.Append(""); + if (!string.IsNullOrEmpty(hasCriticRating.CriticRatingSummary)) + { + builder.Append(""); + } } if (!string.IsNullOrEmpty(item.Overview)) diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs index 31f8f9cb4a..1f20140c50 100644 --- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs @@ -260,15 +260,11 @@ namespace MediaBrowser.Providers.TV if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { - var numBackdrops = item.BackdropImagePaths.Count; - foreach (var image in images.Where(i => i.Type == ImageType.Backdrop)) { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken) + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken) .ConfigureAwait(false); - numBackdrops++; - if (item.BackdropImagePaths.Count >= backdropLimit) break; } } diff --git a/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs b/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs index 77a432add6..05fee78615 100644 --- a/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs @@ -181,8 +181,6 @@ namespace MediaBrowser.Providers.TV if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { - var bdNo = item.BackdropImagePaths.Count; - foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop)) { var url = backdrop.Url; @@ -192,9 +190,7 @@ namespace MediaBrowser.Providers.TV continue; } - await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken).ConfigureAwait(false); - - bdNo++; + await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, null, cancellationToken).ConfigureAwait(false); if (item.BackdropImagePaths.Count >= backdropLimit) break; } diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs index 42118c0633..b56b50bec3 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs @@ -191,8 +191,6 @@ namespace MediaBrowser.Providers.TV if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit) { - var bdNo = item.BackdropImagePaths.Count; - foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop && (!i.Width.HasValue || i.Width.Value >= ConfigurationManager.Configuration.MinSeriesBackdropDownloadWidth))) @@ -204,9 +202,7 @@ namespace MediaBrowser.Providers.TV continue; } - await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken).ConfigureAwait(false); - - bdNo++; + await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, null, cancellationToken).ConfigureAwait(false); if (item.BackdropImagePaths.Count >= backdropLimit) break; } diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index a439251db7..78dcf6fd0d 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -3,11 +3,11 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -25,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// /// Class ImageProcessor /// - public class ImageProcessor : IImageProcessor + public class ImageProcessor : IImageProcessor, IDisposable { /// /// The us culture @@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// /// The _cached imaged sizes /// - private readonly ConcurrentDictionary _cachedImagedSizes = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _cachedImagedSizes; /// /// Gets the list of currently registered image processors @@ -50,21 +50,41 @@ namespace MediaBrowser.Server.Implementations.Drawing private readonly ILogger _logger; private readonly IFileSystem _fileSystem; + private readonly IJsonSerializer _jsonSerializer; + private readonly IServerApplicationPaths _appPaths; private readonly string _imageSizeCachePath; private readonly string _croppedWhitespaceImageCachePath; private readonly string _enhancedImageCachePath; private readonly string _resizedImageCachePath; - public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem) + public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer) { _logger = logger; _fileSystem = fileSystem; + _jsonSerializer = jsonSerializer; + _appPaths = appPaths; _imageSizeCachePath = Path.Combine(appPaths.ImageCachePath, "image-sizes"); _croppedWhitespaceImageCachePath = Path.Combine(appPaths.ImageCachePath, "cropped-images"); _enhancedImageCachePath = Path.Combine(appPaths.ImageCachePath, "enhanced-images"); _resizedImageCachePath = Path.Combine(appPaths.ImageCachePath, "resized-images"); + + _saveImageSizeTimer = new Timer(SaveImageSizeCallback, null, Timeout.Infinite, Timeout.Infinite); + + Dictionary sizeDictionary; + + try + { + sizeDictionary = jsonSerializer.DeserializeFromFile>(ImageSizeFile); + } + catch (IOException) + { + // No biggie + sizeDictionary = new Dictionary(); + } + + _cachedImagedSizes = new ConcurrentDictionary(sizeDictionary); } public void AddParts(IEnumerable enhancers) @@ -85,6 +105,17 @@ namespace MediaBrowser.Server.Implementations.Drawing } var originalImagePath = options.OriginalImagePath; + + if (options.HasDefaultOptions()) + { + // Just spit out the original file if all the options are default + using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) + { + await fileStream.CopyToAsync(toStream).ConfigureAwait(false); + return; + } + } + var dateModified = options.OriginalImageDateModified; if (options.CropWhiteSpace) @@ -106,6 +137,16 @@ namespace MediaBrowser.Server.Implementations.Drawing // Determine the output size based on incoming parameters var newSize = DrawingUtils.Resize(originalImageSize, options.Width, options.Height, options.MaxWidth, options.MaxHeight); + if (options.HasDefaultOptionsWithoutSize() && newSize.Equals(originalImageSize)) + { + // Just spit out the original file the new size equals the old + using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) + { + await fileStream.CopyToAsync(toStream).ConfigureAwait(false); + return; + } + } + var quality = options.Quality ?? 90; var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, options.OutputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.BackgroundColor); @@ -465,11 +506,13 @@ namespace MediaBrowser.Server.Implementations.Drawing ImageSize size; - if (!_cachedImagedSizes.TryGetValue(name, out size)) + var cacheHash = name.GetMD5(); + + if (!_cachedImagedSizes.TryGetValue(cacheHash, out size)) { - size = GetImageSizeInternal(name, path); + size = GetImageSizeInternal(path); - _cachedImagedSizes.AddOrUpdate(name, size, (keyName, oldValue) => size); + _cachedImagedSizes.AddOrUpdate(cacheHash, size, (keyName, oldValue) => size); } return size; @@ -478,62 +521,47 @@ namespace MediaBrowser.Server.Implementations.Drawing /// /// Gets the image size internal. /// - /// The cache key. /// The path. /// ImageSize. - private ImageSize GetImageSizeInternal(string cacheKey, string path) + private ImageSize GetImageSizeInternal(string path) { - // Now check the file system cache - var fullCachePath = GetCachePath(_imageSizeCachePath, cacheKey, ".txt"); + var size = ImageHeader.GetDimensions(path, _logger, _fileSystem); - try - { - var result = File.ReadAllText(fullCachePath).Split('|'); + StartSaveImageSizeTimer(); - return new ImageSize - { - Width = double.Parse(result[0], UsCulture), - Height = double.Parse(result[1], UsCulture) - }; - } - catch (IOException) - { - // Cache file doesn't exist or is currently being written to - } + return new ImageSize { Width = size.Width, Height = size.Height }; + } - var syncLock = GetObjectLock(fullCachePath); + private readonly Timer _saveImageSizeTimer; + private const int SaveImageSizeTimeout = 5000; + private readonly object _saveImageSizeLock = new object(); + private void StartSaveImageSizeTimer() + { + _saveImageSizeTimer.Change(SaveImageSizeTimeout, Timeout.Infinite); + } - lock (syncLock) + private void SaveImageSizeCallback(object state) + { + lock (_saveImageSizeLock) { try { - var result = File.ReadAllText(fullCachePath).Split('|'); - - return new ImageSize - { - Width = double.Parse(result[0], UsCulture), - Height = double.Parse(result[1], UsCulture) - }; - } - catch (FileNotFoundException) - { - // Cache file doesn't exist no biggie + var path = ImageSizeFile; + Directory.CreateDirectory(Path.GetDirectoryName(path)); + _jsonSerializer.SerializeToFile(_cachedImagedSizes, path); } - catch (DirectoryNotFoundException) + catch (Exception ex) { - // Cache file doesn't exist no biggie + _logger.ErrorException("Error saving image size file", ex); } + } + } - var size = ImageHeader.GetDimensions(path, _logger, _fileSystem); - - var parentPath = Path.GetDirectoryName(fullCachePath); - - Directory.CreateDirectory(parentPath); - - // Update the file system cache - File.WriteAllText(fullCachePath, size.Width.ToString(UsCulture) + @"|" + size.Height.ToString(UsCulture)); - - return new ImageSize { Width = size.Width, Height = size.Height }; + private string ImageSizeFile + { + get + { + return Path.Combine(_appPaths.DataPath, "imagesizes.json"); } } @@ -862,5 +890,10 @@ namespace MediaBrowser.Server.Implementations.Drawing }).ToList(); } + + public void Dispose() + { + _saveImageSizeTimer.Dispose(); + } } } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 19e19f8ea4..0104196e01 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -792,11 +792,15 @@ namespace MediaBrowser.Server.Implementations.Dto dto.MediaType = item.MediaType; dto.LocationType = item.LocationType; - dto.CriticRating = item.CriticRating; - - if (fields.Contains(ItemFields.CriticRatingSummary)) + var hasCriticRating = item as IHasCriticRating; + if (hasCriticRating != null) { - dto.CriticRatingSummary = item.CriticRatingSummary; + dto.CriticRating = hasCriticRating.CriticRating; + + if (fields.Contains(ItemFields.CriticRatingSummary)) + { + dto.CriticRatingSummary = hasCriticRating.CriticRatingSummary; + } } var localTrailerCount = item.LocalTrailerIds.Count; diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs index 5f354fb0df..7d049549b5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs @@ -181,6 +181,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// The dto. private void FilterResponse(IHttpRequest req, IHttpResponse res, object dto) { + // Try to prevent compatibility view + res.AddHeader("X-UA-Compatible", "IE=Edge"); + var exception = dto as Exception; if (exception != null) diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs index 9fc622c213..a8c923bb0a 100644 --- a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs +++ b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs @@ -363,6 +363,7 @@ namespace MediaBrowser.Server.Implementations.IO { if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase)) { + Logger.Debug("Watcher ignoring change to {0}", e.FullPath); return true; } @@ -370,6 +371,7 @@ namespace MediaBrowser.Server.Implementations.IO var parent = Path.GetDirectoryName(i); if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) { + Logger.Debug("Watcher ignoring change to {0}", e.FullPath); return true; } @@ -379,10 +381,18 @@ namespace MediaBrowser.Server.Implementations.IO parent = Path.GetDirectoryName(i); if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) { + Logger.Debug("Watcher ignoring change to {0}", e.FullPath); return true; } } + if (i.StartsWith(e.FullPath, StringComparison.OrdinalIgnoreCase) || + e.FullPath.StartsWith(i, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Watcher ignoring change to {0}", e.FullPath); + return true; + } + return false; })) @@ -390,12 +400,6 @@ namespace MediaBrowser.Server.Implementations.IO return; } - if (tempIgnorePaths.Contains(e.FullPath, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher requested to ignore change to " + e.FullPath); - return; - } - Logger.Info("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); //Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index f464985991..0ed46e7610 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -8,6 +7,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -70,6 +70,15 @@ namespace MediaBrowser.Server.Implementations.Providers throw new ArgumentNullException("mimeType"); } + if (type == ImageType.Backdrop && imageIndex == null) + { + imageIndex = item.BackdropImagePaths.Count; + } + else if (type == ImageType.Screenshot && imageIndex == null) + { + imageIndex = item.ScreenshotImagePaths.Count; + } + var saveLocally = _config.Configuration.SaveLocalMeta && item.Parent != null && !(item is Audio); if (item is IItemByName || item is User) diff --git a/MediaBrowser.Server.Implementations/Sorting/CriticRatingComparer.cs b/MediaBrowser.Server.Implementations/Sorting/CriticRatingComparer.cs index 9484130cbc..d01f7ed1b3 100644 --- a/MediaBrowser.Server.Implementations/Sorting/CriticRatingComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -22,7 +22,9 @@ namespace MediaBrowser.Server.Implementations.Sorting private float GetValue(BaseItem x) { - return x.CriticRating ?? 0; + var hasCriticRating = x as IHasCriticRating; + + return hasCriticRating == null ? 0 : hasCriticRating.CriticRating ?? 0; } /// diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config index 4f60de1457..ba6d742140 100644 --- a/MediaBrowser.ServerApplication/App.config +++ b/MediaBrowser.ServerApplication/App.config @@ -19,6 +19,7 @@ + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index c5c5bc7e53..03819ff912 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -288,7 +288,7 @@ namespace MediaBrowser.ServerApplication LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager); RegisterSingleInstance(LocalizationManager); - ImageProcessor = new ImageProcessor(Logger, ServerConfigurationManager.ApplicationPaths, FileSystemManager); + ImageProcessor = new ImageProcessor(Logger, ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer); RegisterSingleInstance(ImageProcessor); DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index be821922fe..b18d4c18a5 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -375,9 +375,9 @@ namespace MediaBrowser.WebDashboard.Api { var sb = new StringBuilder(); + sb.Append(""); sb.Append(""); sb.Append(""); - sb.Append(""); //sb.Append(""); // http://developer.apple.com/library/ios/#DOCUMENTATION/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index ab32f9097c..34739b26d2 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.237 + 3.0.238 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 54ddf0303a..f93c862eb1 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.237 + 3.0.238 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 6237b923a5..7a72162999 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.237 + 3.0.238 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - +