using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; 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.Globalization; 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 { protected readonly IFileSystem FileSystem; public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) : base(logManager, configurationManager) { FileSystem = fileSystem; } 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; } protected override IEnumerable GetItemsForFileStampComparison(BaseItem item) { var season = item as Season; if (season != null) { var series = season.Series; if (series != null) { return new[] { item, series }; } } return base.GetItemsForFileStampComparison(item); } /// /// 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, BaseProviderInfo providerInfo, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // Make sure current image paths still exist item.ValidateImages(); cancellationToken.ThrowIfCancellationRequested(); // Make sure current backdrop paths still exist item.ValidateBackdrops(); var hasScreenshots = item as IHasScreenshots; if (hasScreenshots != null) { hasScreenshots.ValidateScreenshots(); } cancellationToken.ThrowIfCancellationRequested(); var args = GetResolveArgsContainingImages(item); PopulateBaseItemImages(item, args); SetLastRefreshed(item, DateTime.UtcNow, providerInfo); return TrueTaskResult; } private ItemResolveArgs GetResolveArgsContainingImages(BaseItem item) { if (item.IsInMixedFolder) { if (item.Parent == null) { return item.ResolveArgs; } return item.Parent.ResolveArgs; } return item.ResolveArgs; } /// /// 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 FileSystemInfo GetImage(List files, string filenameWithoutExtension) { return BaseItem.SupportedImageExtensions .Select(i => files.FirstOrDefault(f => string.Equals(f.Extension, i, StringComparison.OrdinalIgnoreCase) && string.Equals(filenameWithoutExtension, Path.GetFileNameWithoutExtension(f.Name), StringComparison.OrdinalIgnoreCase))) .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); } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); /// /// Fills in image paths based on files win the folder /// /// The item. /// The args. private void PopulateBaseItemImages(BaseItem item, ItemResolveArgs args) { PopulatePrimaryImage(item, args); // Logo Image var image = GetImage(item, args, "logo"); if (image != null) { item.SetImagePath(ImageType.Logo, image.FullName); } // Clearart image = GetImage(item, args, "clearart"); if (image != null) { item.SetImagePath(ImageType.Art, image.FullName); } // Disc image = GetImage(item, args, "disc") ?? GetImage(item, args, "cdart"); if (image != null) { item.SetImagePath(ImageType.Disc, image.FullName); } // Box Image image = GetImage(item, args, "box"); if (image != null) { item.SetImagePath(ImageType.Box, image.FullName); } // BoxRear Image image = GetImage(item, args, "boxrear"); if (image != null) { item.SetImagePath(ImageType.BoxRear, image.FullName); } // Thumbnail Image image = GetImage(item, args, "menu"); if (image != null) { item.SetImagePath(ImageType.Menu, image.FullName); } PopulateBanner(item, args); PopulateThumb(item, args); // Backdrop Image PopulateBackdrops(item, args); PopulateScreenshots(item, args); } private void PopulatePrimaryImage(BaseItem item, ItemResolveArgs args) { // Primary Image var image = GetImage(item, args, "folder") ?? GetImage(item, args, "poster") ?? GetImage(item, args, "cover") ?? GetImage(item, args, "default"); // Support plex/xbmc convention if (image == null && item is Series) { image = GetImage(item, args, "show"); } var isFileSystemItem = item.LocationType == LocationType.FileSystem; // Support plex/xbmc convention if (image == null) { // Supprt xbmc conventions var season = item as Season; if (season != null && item.IndexNumber.HasValue && isFileSystemItem) { image = GetSeasonImageFromSeriesFolder(season, "-poster"); } } // Support plex/xbmc convention if (image == null && (item is Movie || item is MusicVideo || item is AdultVideo)) { image = GetImage(item, args, "movie"); } // Look for a file with the same name as the item if (image == null && isFileSystemItem) { var name = Path.GetFileNameWithoutExtension(item.Path); if (!string.IsNullOrEmpty(name)) { image = GetImage(item, args, name) ?? GetImage(item, args, name + "-poster"); } } if (image != null) { item.SetImagePath(ImageType.Primary, image.FullName); } } /// /// Populates the banner. /// /// The item. /// The args. private void PopulateBanner(BaseItem item, ItemResolveArgs args) { // Banner Image var image = GetImage(item, args, "banner"); if (image == null) { var isFileSystemItem = item.LocationType == LocationType.FileSystem; // Supprt xbmc conventions var season = item as Season; if (season != null && item.IndexNumber.HasValue && isFileSystemItem) { image = GetSeasonImageFromSeriesFolder(season, "-banner"); } } if (image != null) { item.SetImagePath(ImageType.Banner, image.FullName); } } /// /// Populates the thumb. /// /// The item. /// The args. private void PopulateThumb(BaseItem item, ItemResolveArgs args) { // Thumbnail Image var image = GetImage(item, args, "thumb"); if (image == null) { var isFileSystemItem = item.LocationType == LocationType.FileSystem; // Supprt xbmc conventions var season = item as Season; if (season != null && item.IndexNumber.HasValue && isFileSystemItem) { image = GetSeasonImageFromSeriesFolder(season, "-landscape"); } } if (image != null) { item.SetImagePath(ImageType.Thumb, image.FullName); } } /// /// Populates the backdrops. /// /// The item. /// The args. private void PopulateBackdrops(BaseItem item, ItemResolveArgs args) { var isFileSystemItem = item.LocationType == LocationType.FileSystem; var backdropFiles = new List(); PopulateBackdrops(item, args, backdropFiles, "backdrop", "backdrop"); // Support {name}-fanart.ext if (isFileSystemItem) { var name = Path.GetFileNameWithoutExtension(item.Path); if (!string.IsNullOrEmpty(name)) { var image = GetImage(item, args, name + "-fanart"); if (image != null) { backdropFiles.Add(image.FullName); } } } // Support plex/xbmc conventions PopulateBackdrops(item, args, backdropFiles, "fanart", "fanart-"); PopulateBackdrops(item, args, backdropFiles, "background", "background-"); PopulateBackdrops(item, args, backdropFiles, "art", "art-"); var season = item as Season; if (season != null && item.IndexNumber.HasValue && isFileSystemItem) { var image = GetSeasonImageFromSeriesFolder(season, "-fanart"); if (image != null) { backdropFiles.Add(image.FullName); } } if (isFileSystemItem) { PopulateBackdropsFromExtraFanart(args, backdropFiles); } if (backdropFiles.Count > 0) { item.BackdropImagePaths = backdropFiles; } } private FileSystemInfo GetSeasonImageFromSeriesFolder(Season season, string imageSuffix) { var series = season.Series; var seriesFolderArgs = series.ResolveArgs; var seasonNumber = season.IndexNumber; string filename = null; FileSystemInfo image; if (seasonNumber.HasValue) { var seasonMarker = seasonNumber.Value == 0 ? "-specials" : seasonNumber.Value.ToString("00", _usCulture); // Get this one directly from the file system since we have to go up a level filename = "season" + seasonMarker + imageSuffix; image = GetImage(series, seriesFolderArgs, filename); if (image != null && image.Exists) { return image; } } var previousFilename = filename; // Try using the season name filename = season.Name.ToLower().Replace(" ", string.Empty) + imageSuffix; if (!string.Equals(previousFilename, filename)) { image = GetImage(series, seriesFolderArgs, filename); if (image != null && image.Exists) { return image; } } return null; } /// /// Populates the backdrops from extra fanart. /// /// The args. /// The backdrops. private void PopulateBackdropsFromExtraFanart(ItemResolveArgs args, List backdrops) { if (!args.IsDirectory) { return; } if (args.ContainsFileSystemEntryByName("extrafanart")) { var path = Path.Combine(args.Path, "extrafanart"); var imageFiles = Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly) .Where(i => { var extension = Path.GetExtension(i); if (string.IsNullOrEmpty(extension)) { return false; } return BaseItem.SupportedImageExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); }) .ToList(); backdrops.AddRange(imageFiles); } } /// /// 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; } } } } /// /// Populates the screenshots. /// /// The item. /// The args. private void PopulateScreenshots(BaseItem item, ItemResolveArgs args) { // Screenshot Image var 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) { var hasScreenshots = item as IHasScreenshots; if (hasScreenshots != null) { hasScreenshots.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; } } } }