diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-10-29 18:01:02 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-10-29 18:01:02 -0400 |
| commit | e33244d7971f0299cd21297597da6181d01631e9 (patch) | |
| tree | b11fc639998458f3b8d95b54538bd5dc0e6868bd /MediaBrowser.Server.Implementations/Photos | |
| parent | 5eec770ae2ea61597f6e80877860d48f540b78e8 (diff) | |
improve user view images
Diffstat (limited to 'MediaBrowser.Server.Implementations/Photos')
3 files changed, 346 insertions, 322 deletions
diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs new file mode 100644 index 000000000..1abefdef1 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -0,0 +1,189 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Photos +{ + public abstract class BaseDynamicImageProvider<T> : IHasChangeMonitor + where T : IHasImages + { + protected IFileSystem FileSystem { get; private set; } + protected IProviderManager ProviderManager { get; private set; } + + protected BaseDynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager) + { + ProviderManager = providerManager; + FileSystem = fileSystem; + } + + public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) + { + if (!Supports(item)) + { + return ItemUpdateType.None; + } + + var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false); + var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false); + + return primaryResult | thumbResult; + } + + protected virtual bool Supports(IHasImages item) + { + return true; + } + + protected abstract Task<List<BaseItem>> GetItemsWithImages(IHasImages item); + + private const string Version = "3"; + protected string GetConfigurationCacheKey(List<BaseItem> items) + { + return (Version + "_" + string.Join(",", items.Select(i => i.Id.ToString("N")).ToArray())).GetMD5().ToString("N"); + } + + protected async Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken) + { + var items = await GetItemsWithImages(item).ConfigureAwait(false); + var cacheKey = GetConfigurationCacheKey(items); + + if (!HasChanged(item, imageType, cacheKey)) + { + return ItemUpdateType.None; + } + + return await FetchAsyncInternal(item, items, imageType, cacheKey, options, cancellationToken).ConfigureAwait(false); + } + + protected async Task<ItemUpdateType> FetchAsyncInternal(IHasImages item, + List<BaseItem> itemsWithImages, + ImageType imageType, + string cacheKey, + MetadataRefreshOptions options, + CancellationToken cancellationToken) + { + var img = await CreateImageAsync(item, itemsWithImages, imageType, 0).ConfigureAwait(false); + + if (img == null) + { + return ItemUpdateType.None; + } + + using (var ms = new MemoryStream()) + { + img.Save(ms, ImageFormat.Png); + + ms.Position = 0; + + await ProviderManager.SaveImage(item, ms, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false); + } + + return ItemUpdateType.ImageUpdate; + } + + protected Task<Image> GetThumbCollage(List<BaseItem> items) + { + return DynamicImageHelpers.GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList(), + FileSystem, + 1600, + 900); + } + + protected Task<Image> GetSquareCollage(List<BaseItem> items) + { + return DynamicImageHelpers.GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList(), + FileSystem, + 800); + } + + public string Name + { + get { return "Dynamic Image Provider"; } + } + + public async Task<Image> CreateImageAsync(IHasImages item, + List<BaseItem> itemsWithImages, + ImageType imageType, + int imageIndex) + { + if (itemsWithImages.Count == 0) + { + return null; + } + + return imageType == ImageType.Thumb ? + await GetThumbCollage(itemsWithImages).ConfigureAwait(false) : + await GetSquareCollage(itemsWithImages).ConfigureAwait(false); + } + + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) + { + if (!Supports(item)) + { + return false; + } + + var items = GetItemsWithImages(item).Result; + var cacheKey = GetConfigurationCacheKey(items); + + return HasChanged(item, ImageType.Primary, cacheKey) || HasChanged(item, ImageType.Thumb, cacheKey); + } + + protected bool HasChanged(IHasImages item, ImageType type, string cacheKey) + { + var image = item.GetImageInfo(type, 0); + + if (image != null) + { + if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path)) + { + return false; + } + + var currentPathCacheKey = (Path.GetFileNameWithoutExtension(image.Path) ?? string.Empty).Split('_').LastOrDefault(); + + if (string.Equals(cacheKey, currentPathCacheKey, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return true; + } + + protected List<BaseItem> GetFinalItems(List<BaseItem> items) + { + // Rotate the images no more than once per week + var random = new Random(GetWeekOfYear()).Next(); + + return items + .OrderBy(i => random - items.IndexOf(i)) + .Take(4) + .OrderBy(i => i.Name) + .ToList(); + } + + private int GetWeekOfYear() + { + var usCulture = new CultureInfo("en-US"); + var weekNo = usCulture.Calendar.GetWeekOfYear( + DateTime.Now, + usCulture.DateTimeFormat.CalendarWeekRule, + usCulture.DateTimeFormat.FirstDayOfWeek); + + return weekNo; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs b/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs new file mode 100644 index 000000000..2c5cedf65 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs @@ -0,0 +1,147 @@ +using MediaBrowser.Common.IO; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Photos +{ + public static class DynamicImageHelpers + { + public static async Task<Image> GetThumbCollage(List<string> files, + IFileSystem fileSystem, + int width, + int height) + { + if (files.Count < 3) + { + return await GetSingleImage(files, fileSystem).ConfigureAwait(false); + } + + const int rows = 1; + const int cols = 3; + + int cellWidth = 2 * (width / 3); + int cellHeight = height; + var index = 0; + + var img = new Bitmap(width, height, PixelFormat.Format32bppPArgb); + + using (var graphics = Graphics.FromImage(img)) + { + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingMode = CompositingMode.SourceCopy; + + for (var row = 0; row < rows; row++) + { + for (var col = 0; col < cols; col++) + { + var x = col * (cellWidth / 2); + var y = row * cellHeight; + + if (files.Count > index) + { + using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true)) + { + using (var memoryStream = new MemoryStream()) + { + await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); + + memoryStream.Position = 0; + + using (var imgtemp = Image.FromStream(memoryStream, true, false)) + { + graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight); + } + } + } + } + + index++; + } + } + } + + return img; + } + + public static async Task<Image> GetSquareCollage(List<string> files, + IFileSystem fileSystem, + int size) + { + if (files.Count < 4) + { + return await GetSingleImage(files, fileSystem).ConfigureAwait(false); + } + + const int rows = 2; + const int cols = 2; + + int singleSize = size / 2; + var index = 0; + + var img = new Bitmap(size, size, PixelFormat.Format32bppPArgb); + + using (var graphics = Graphics.FromImage(img)) + { + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingMode = CompositingMode.SourceCopy; + + for (var row = 0; row < rows; row++) + { + for (var col = 0; col < cols; col++) + { + var x = col * singleSize; + var y = row * singleSize; + + using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true)) + { + using (var memoryStream = new MemoryStream()) + { + await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); + + memoryStream.Position = 0; + + using (var imgtemp = Image.FromStream(memoryStream, true, false)) + { + graphics.DrawImage(imgtemp, x, y, singleSize, singleSize); + } + } + } + + index++; + } + } + } + + return img; + } + + private static Task<Image> GetSingleImage(List<string> files, IFileSystem fileSystem) + { + return GetImage(files[0], fileSystem); + } + + private static async Task<Image> GetImage(string file, IFileSystem fileSystem) + { + using (var fileStream = fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true)) + { + var memoryStream = new MemoryStream(); + + await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); + + memoryStream.Position = 0; + + return Image.FromStream(memoryStream, true, false); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Photos/PhotoAlbumImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/PhotoAlbumImageProvider.cs index 30e57106c..7c10f2767 100644 --- a/MediaBrowser.Server.Implementations/Photos/PhotoAlbumImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/PhotoAlbumImageProvider.cs @@ -1,337 +1,25 @@ -using System; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Playlists; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MoreLinq; namespace MediaBrowser.Server.Implementations.Photos { - public class PhotoAlbumImageProvider : ICustomMetadataProvider<PhotoAlbum>, IHasChangeMonitor + public class PhotoAlbumImageProvider : BaseDynamicImageProvider<PhotoAlbum>, ICustomMetadataProvider<PhotoAlbum> { - private readonly IFileSystem _fileSystem; - private readonly IProviderManager _provider; - - public PhotoAlbumImageProvider(IFileSystem fileSystem, IProviderManager provider) - { - _fileSystem = fileSystem; - _provider = provider; - } - - public async Task<ItemUpdateType> FetchAsync(PhotoAlbum item, MetadataRefreshOptions options, CancellationToken cancellationToken) - { - var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false); - var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false); - - return primaryResult | thumbResult; - } - - private Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken) - { - var items = GetItemsWithImages(item); - var cacheKey = GetConfigurationCacheKey(items); - - if (!HasChanged(item, imageType, cacheKey)) - { - return Task.FromResult(ItemUpdateType.None); - } - - return FetchAsyncInternal(item, imageType, cacheKey, options, cancellationToken); - } - - private async Task<ItemUpdateType> FetchAsyncInternal(IHasImages item, ImageType imageType, string cacheKey, MetadataRefreshOptions options, CancellationToken cancellationToken) - { - var img = await CreateImageAsync(item, imageType, 0).ConfigureAwait(false); - - if (img == null) - { - return ItemUpdateType.None; - } - - using (var ms = new MemoryStream()) - { - img.Save(ms, ImageFormat.Png); - - ms.Position = 0; - - await _provider.SaveImage(item, ms, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false); - } - - return ItemUpdateType.ImageUpdate; - } - - private bool HasChanged(IHasImages item, ImageType type, string cacheKey) - { - var image = item.GetImageInfo(type, 0); - - if (image != null) - { - if (!_fileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path)) - { - return false; - } - - var currentPathCacheKey = (Path.GetFileNameWithoutExtension(image.Path) ?? string.Empty).Split('_').LastOrDefault(); - - if (string.Equals(cacheKey, currentPathCacheKey, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return true; - } - - private const string Version = "3"; - - public string GetConfigurationCacheKey(List<BaseItem> items) - { - return (Version + "_" + string.Join(",", items.Select(i => i.Id.ToString("N")).ToArray())).GetMD5().ToString("N"); - } - - private List<BaseItem> GetItemsWithImages(IHasImages item) - { - var photoAlbum = item as PhotoAlbum; - if (photoAlbum != null) - { - return GetFinalItems(photoAlbum.RecursiveChildren.Where(i => i is Photo).ToList()); - } - - var playlist = (Playlist)item; - - var items = playlist.GetManageableItems() - .Select(i => - { - var subItem = i.Item2; - - var episode = subItem as Episode; - - if (episode != null) - { - var series = episode.Series; - if (series != null && series.HasImage(ImageType.Primary)) - { - return series; - } - } - - if (subItem.HasImage(ImageType.Primary)) - { - return subItem; - } - - var parent = subItem.Parent; - - if (parent != null && parent.HasImage(ImageType.Primary)) - { - if (parent is MusicAlbum) - { - return parent; - } - } - - return null; - }) - .Where(i => i != null) - .DistinctBy(i => i.Id) - .ToList(); - - return GetFinalItems(items); - } - - private List<BaseItem> GetFinalItems(List<BaseItem> items) - { - // Rotate the images no more than once per day - var random = new Random(DateTime.Now.DayOfYear).Next(); - - return items - .OrderBy(i => random - items.IndexOf(i)) - .Take(4) - .OrderBy(i => i.Name) - .ToList(); - } - - public async Task<Image> CreateImageAsync(IHasImages item, ImageType imageType, int imageIndex) - { - var items = GetItemsWithImages(item); - - if (items.Count == 0) - { - return null; - } - - return imageType == ImageType.Thumb ? - await GetThumbCollage(items).ConfigureAwait(false) : - await GetSquareCollage(items).ConfigureAwait(false); - } - - private Task<Image> GetThumbCollage(List<BaseItem> items) - { - return GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList()); - } - - private Task<Image> GetSquareCollage(List<BaseItem> items) - { - return GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList()); - } - - private async Task<Image> GetThumbCollage(List<string> files) - { - if (files.Count < 3) - { - return await GetSingleImage(files).ConfigureAwait(false); - } - - const int rows = 1; - const int cols = 3; - - const int cellWidth = 2 * (ThumbImageWidth / 3); - const int cellHeight = ThumbImageHeight; - var index = 0; - - var img = new Bitmap(ThumbImageWidth, ThumbImageHeight, PixelFormat.Format32bppPArgb); - - using (var graphics = Graphics.FromImage(img)) - { - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingMode = CompositingMode.SourceCopy; - - for (var row = 0; row < rows; row++) - { - for (var col = 0; col < cols; col++) - { - var x = col * (cellWidth / 2); - var y = row * cellHeight; - - if (files.Count > index) - { - using (var fileStream = _fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true)) - { - using (var memoryStream = new MemoryStream()) - { - await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); - - memoryStream.Position = 0; - - using (var imgtemp = Image.FromStream(memoryStream, true, false)) - { - graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight); - } - } - } - } - - index++; - } - } - } - - return img; - } - - private const int SquareImageSize = 800; - private const int ThumbImageWidth = 1600; - private const int ThumbImageHeight = 900; - - private async Task<Image> GetSquareCollage(List<string> files) - { - if (files.Count < 4) - { - return await GetSingleImage(files).ConfigureAwait(false); - } - - const int rows = 2; - const int cols = 2; - - const int singleSize = SquareImageSize / 2; - var index = 0; - - var img = new Bitmap(SquareImageSize, SquareImageSize, PixelFormat.Format32bppPArgb); - - using (var graphics = Graphics.FromImage(img)) - { - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingMode = CompositingMode.SourceCopy; - - for (var row = 0; row < rows; row++) - { - for (var col = 0; col < cols; col++) - { - var x = col * singleSize; - var y = row * singleSize; - - using (var fileStream = _fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true)) - { - using (var memoryStream = new MemoryStream()) - { - await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); - - memoryStream.Position = 0; - - using (var imgtemp = Image.FromStream(memoryStream, true, false)) - { - graphics.DrawImage(imgtemp, x, y, singleSize, singleSize); - } - } - } - - index++; - } - } - } - - return img; - } - - private Task<Image> GetSingleImage(List<string> files) - { - return GetImage(files[0]); - } - - private async Task<Image> GetImage(string file) - { - using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true)) - { - var memoryStream = new MemoryStream(); - - await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); - - memoryStream.Position = 0; - - return Image.FromStream(memoryStream, true, false); - } - } - - public string Name + public PhotoAlbumImageProvider(IFileSystem fileSystem, IProviderManager providerManager) + : base(fileSystem, providerManager) { - get { return "Dynamic Image Provider"; } } - public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) + protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item) { - var items = GetItemsWithImages(item); - var cacheKey = GetConfigurationCacheKey(items); + var photoAlbum = (PhotoAlbum)item; + var items = GetFinalItems(photoAlbum.RecursiveChildren.Where(i => i is Photo).ToList()); - return HasChanged(item, ImageType.Primary, cacheKey) || HasChanged(item, ImageType.Thumb, cacheKey); + return Task.FromResult(items); } } } |
