using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Providers { /// /// Provides images for all types by looking for standard images - folder, backdrop, logo, etc. /// public class ImageFromMediaLocationProvider : BaseMetadataProvider { public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) { } public override ItemUpdateType ItemUpdateType { get { return ItemUpdateType.ImageUpdate; } } /// /// Supportses the specified item. /// /// The item. /// true if XXXX, false otherwise public override bool Supports(BaseItem item) { if (item.LocationType == LocationType.FileSystem) { if (item.ResolveArgs.IsDirectory) { return true; } return item.IsInMixedFolder && item.Parent != null && !(item is Episode); } return false; } /// /// Gets the priority. /// /// The priority. public override MetadataProviderPriority Priority { get { return MetadataProviderPriority.First; } } /// /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes /// /// true if [refresh on file system stamp change]; otherwise, false. protected override bool RefreshOnFileSystemStampChange { get { return true; } } /// /// Gets the filestamp extensions. /// /// The filestamp extensions. protected override string[] FilestampExtensions { get { return BaseItem.SupportedImageExtensions; } } /// /// 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 Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var args = GetResolveArgsContainingImages(item); // Make sure current image paths still exist ValidateImages(item); cancellationToken.ThrowIfCancellationRequested(); // Make sure current backdrop paths still exist ValidateBackdrops(item); ValidateScreenshots(item, args); cancellationToken.ThrowIfCancellationRequested(); PopulateBaseItemImages(item, args); SetLastRefreshed(item, DateTime.UtcNow); return TrueTaskResult; } private ItemResolveArgs GetResolveArgsContainingImages(BaseItem item) { if (item.IsInMixedFolder) { if (item.Parent == null) { return item.ResolveArgs; } return item.Parent.ResolveArgs; } return item.ResolveArgs; } /// /// Validates that images within the item are still on the file system /// /// The item. internal static void ValidateImages(BaseItem item) { // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below var deletedKeys = item.Images .ToList() .Where(image => !File.Exists(image.Value)) .Select(i => i.Key) .ToList(); // Now remove them from the dictionary foreach (var key in deletedKeys) { item.Images.Remove(key); } } /// /// Validates that backdrops within the item are still on the file system /// /// The item. internal static void ValidateBackdrops(BaseItem item) { // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below var deletedImages = item.BackdropImagePaths .Where(path => !File.Exists(path)) .ToList(); // Now remove them from the dictionary foreach (var path in deletedImages) { item.BackdropImagePaths.Remove(path); } } /// /// Validates the screenshots. /// /// The item. /// The args. private void ValidateScreenshots(BaseItem item, ItemResolveArgs args) { // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below var deletedImages = item.ScreenshotImagePaths .Where(path => !File.Exists(path)) .ToList(); // Now remove them from the dictionary foreach (var path in deletedImages) { item.ScreenshotImagePaths.Remove(path); } } /// /// Determines whether [is in same directory] [the specified item]. /// /// The item. /// The path. /// true if [is in same directory] [the specified item]; otherwise, false. private bool IsInMetaLocation(BaseItem item, string path) { return string.Equals(Path.GetDirectoryName(path), item.MetaLocation, StringComparison.OrdinalIgnoreCase); } /// /// Gets the image. /// /// The item. /// The args. /// The filename without extension. /// FileSystemInfo. protected virtual FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension) { return BaseItem.SupportedImageExtensions .Select(i => args.GetMetaFileByPath(GetFullImagePath(item, args, filenameWithoutExtension, i))) .FirstOrDefault(i => i != null); } protected virtual string GetFullImagePath(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension, string extension) { var path = item.MetaLocation; if (item.IsInMixedFolder) { var pathFilenameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path); // If the image filename and path file name match, just look for an image using the same full path as the item if (string.Equals(pathFilenameWithoutExtension, filenameWithoutExtension)) { return Path.ChangeExtension(item.Path, extension); } return Path.Combine(path, pathFilenameWithoutExtension + "-" + filenameWithoutExtension + extension); } return Path.Combine(path, filenameWithoutExtension + extension); } /// /// Fills in image paths based on files win the folder /// /// The item. /// The args. private void PopulateBaseItemImages(BaseItem item, ItemResolveArgs args) { // Primary Image var image = GetImage(item, args, "folder") ?? GetImage(item, args, "poster") ?? GetImage(item, args, "cover") ?? GetImage(item, args, "default"); // Look for a file with the same name as the item if (image == null) { var name = Path.GetFileNameWithoutExtension(item.Path); if (!string.IsNullOrEmpty(name)) { image = GetImage(item, args, name); } } if (image != null) { item.SetImage(ImageType.Primary, image.FullName); } // Logo Image image = GetImage(item, args, "logo"); if (image != null) { item.SetImage(ImageType.Logo, image.FullName); } // Banner Image image = GetImage(item, args, "banner"); if (image != null) { item.SetImage(ImageType.Banner, image.FullName); } // Clearart image = GetImage(item, args, "clearart"); if (image != null) { item.SetImage(ImageType.Art, image.FullName); } // Disc image = GetImage(item, args, "disc") ?? GetImage(item, args, "cdart"); if (image != null) { item.SetImage(ImageType.Disc, image.FullName); } // Thumbnail Image image = GetImage(item, args, "thumb"); if (image != null) { item.SetImage(ImageType.Thumb, image.FullName); } // Box Image image = GetImage(item, args, "box"); if (image != null) { item.SetImage(ImageType.Box, image.FullName); } // BoxRear Image image = GetImage(item, args, "boxrear"); if (image != null) { item.SetImage(ImageType.BoxRear, image.FullName); } // Thumbnail Image image = GetImage(item, args, "menu"); if (image != null) { item.SetImage(ImageType.Menu, image.FullName); } // Backdrop Image PopulateBackdrops(item, args); // Screenshot Image image = GetImage(item, args, "screenshot"); var screenshotFiles = new List(); if (image != null) { screenshotFiles.Add(image.FullName); } var unfound = 0; for (var i = 1; i <= 20; i++) { // Screenshot Image image = GetImage(item, args, "screenshot" + i); if (image != null) { screenshotFiles.Add(image.FullName); } else { unfound++; if (unfound >= 3) { break; } } } if (screenshotFiles.Count > 0) { item.ScreenshotImagePaths = screenshotFiles; } } /// /// Populates the backdrops. /// /// The item. /// The args. private void PopulateBackdrops(BaseItem item, ItemResolveArgs args) { var backdropFiles = new List(); PopulateBackdrops(item, args, backdropFiles, "backdrop", "backdrop"); // Support plex/xbmc conventions PopulateBackdrops(item, args, backdropFiles, "fanart", "fanart-"); PopulateBackdrops(item, args, backdropFiles, "background", "background-"); if (backdropFiles.Count > 0) { item.BackdropImagePaths = backdropFiles; } } /// /// Populates the backdrops. /// /// The item. /// The args. /// The backdrop files. /// The filename. /// The numbered suffix. private void PopulateBackdrops(BaseItem item, ItemResolveArgs args, List backdropFiles, string filename, string numberedSuffix) { var image = GetImage(item, args, filename); if (image != null) { backdropFiles.Add(image.FullName); } var unfound = 0; for (var i = 1; i <= 20; i++) { // Backdrop Image image = GetImage(item, args, numberedSuffix + i); if (image != null) { backdropFiles.Add(image.FullName); } else { unfound++; if (unfound >= 3) { break; } } } } } }