#nullable disable #pragma warning disable CS1591 using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; namespace Emby.Server.Implementations.Images { public abstract class BaseDynamicImageProvider : IHasItemChangeMonitor, IForcedProvider, ICustomMetadataProvider, IHasOrder where T : BaseItem { protected BaseDynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) { ApplicationPaths = applicationPaths; ProviderManager = providerManager; FileSystem = fileSystem; ImageProcessor = imageProcessor; } protected IFileSystem FileSystem { get; } protected IProviderManager ProviderManager { get; } protected IApplicationPaths ApplicationPaths { get; } protected IImageProcessor ImageProcessor { get; set; } protected virtual IReadOnlyCollection SupportedImages { get; } = new ImageType[] { ImageType.Primary }; /// public string Name => "Dynamic Image Provider"; protected virtual int MaxImageAgeDays => 7; public int Order => 0; protected virtual bool Supports(BaseItem item) => true; public async Task FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) { if (!Supports(item)) { return ItemUpdateType.None; } var updateType = ItemUpdateType.None; if (SupportedImages.Contains(ImageType.Primary)) { var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false); updateType |= primaryResult; } if (SupportedImages.Contains(ImageType.Thumb)) { var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false); updateType |= thumbResult; } return updateType; } protected Task FetchAsync(BaseItem item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken) { var image = item.GetImageInfo(imageType, 0); if (image is not null) { if (!image.IsLocalFile) { return Task.FromResult(ItemUpdateType.None); } if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path)) { return Task.FromResult(ItemUpdateType.None); } } var items = GetItemsWithImages(item); return FetchToFileInternal(item, items, imageType, cancellationToken); } protected async Task FetchToFileInternal( BaseItem item, IReadOnlyList itemsWithImages, ImageType imageType, CancellationToken cancellationToken) { var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); Directory.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension)); string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0); if (string.IsNullOrEmpty(outputPath)) { return ItemUpdateType.None; } var mimeType = MimeTypes.GetMimeType(outputPath); if (string.Equals(mimeType, "application/octet-stream", StringComparison.OrdinalIgnoreCase)) { mimeType = "image/png"; } await ProviderManager.SaveImage(item, outputPath, mimeType, imageType, null, false, cancellationToken).ConfigureAwait(false); return ItemUpdateType.ImageUpdate; } protected abstract IReadOnlyList GetItemsWithImages(BaseItem item); protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 640, 360); } protected virtual IEnumerable GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable items) { var useBackdrop = primaryItem is CollectionFolder || primaryItem is UserView; return items .Select(i => { // Use Backdrop instead of Primary image for Library images. if (useBackdrop) { var backdrop = i.GetImageInfo(ImageType.Backdrop, 0); if (backdrop is not null && backdrop.IsLocalFile) { return backdrop.Path; } } var image = i.GetImageInfo(ImageType.Primary, 0); if (image is not null && image.IsLocalFile) { return image.Path; } image = i.GetImageInfo(ImageType.Thumb, 0); if (image is not null && image.IsLocalFile) { return image.Path; } return null; }) .Where(i => !string.IsNullOrEmpty(i)); } protected string CreatePosterCollage(BaseItem primaryItem, IEnumerable items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 400, 600); } protected string CreateSquareCollage(BaseItem primaryItem, IEnumerable items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 600, 600); } protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable items, string outputPath, int width, int height) { return CreateCollage(primaryItem, items, outputPath, width, height); } private string CreateCollage(BaseItem primaryItem, IEnumerable items, string outputPath, int width, int height) { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); var options = new ImageCollageOptions { Height = height, Width = width, OutputPath = outputPath, InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray() }; if (options.InputPaths.Count == 0) { return null; } if (!ImageProcessor.SupportsImageCollageCreation) { return null; } ImageProcessor.CreateImageCollage(options, primaryItem.Name); return outputPath; } protected virtual string CreateImage( BaseItem item, IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { if (itemsWithImages.Count == 0) { return null; } string outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); if (imageType == ImageType.Thumb) { return CreateThumbCollage(item, itemsWithImages, outputPath); } if (imageType == ImageType.Primary) { if (item is UserView || item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum || item is MusicArtist) { return CreateSquareCollage(item, itemsWithImages, outputPath); } return CreatePosterCollage(item, itemsWithImages, outputPath); } throw new ArgumentException("Unexpected image type", nameof(imageType)); } public bool HasChanged(BaseItem item, IDirectoryService directoryService) { if (!Supports(item)) { return false; } if (SupportedImages.Contains(ImageType.Primary) && HasChanged(item, ImageType.Primary)) { return true; } if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb)) { return true; } return false; } protected bool HasChanged(BaseItem item, ImageType type) { var image = item.GetImageInfo(type, 0); if (image is not null) { if (!image.IsLocalFile) { return false; } if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path)) { return false; } if (!HasChangedByDate(item, image)) { return false; } } return true; } protected virtual bool HasChangedByDate(BaseItem item, ItemImageInfo image) { var age = DateTime.UtcNow - image.DateModified; return age.TotalDays > MaxImageAgeDays; } protected string CreateSingleImage(IEnumerable itemsWithImages, string outputPathWithoutExtension, ImageType imageType) { var image = itemsWithImages .Where(i => i.HasImage(imageType) && i.GetImageInfo(imageType, 0).IsLocalFile && Path.HasExtension(i.GetImagePath(imageType))) .Select(i => i.GetImagePath(imageType)) .FirstOrDefault(); if (string.IsNullOrEmpty(image)) { return null; } var ext = Path.GetExtension(image); var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ext); File.Copy(image, outputPath, true); return outputPath; } } }