Ver código fonte

support null image encoder

Luke Pulverenti 9 anos atrás
pai
commit
c80e1df1ca
24 arquivos alterados com 333 adições e 96 exclusões
  1. 4 0
      Emby.Drawing/Emby.Drawing.csproj
  2. 25 3
      Emby.Drawing/GDI/GDIImageEncoder.cs
  3. BIN
      Emby.Drawing/GDI/empty.png
  4. 12 6
      Emby.Drawing/IImageEncoder.cs
  5. 14 5
      Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
  6. 2 2
      Emby.Drawing/ImageMagick/StripCollageBuilder.cs
  7. 20 1
      Emby.Drawing/ImageProcessor.cs
  8. 64 0
      Emby.Drawing/NullImageEncoder.cs
  9. 42 16
      MediaBrowser.Api/Images/ImageService.cs
  10. 10 6
      MediaBrowser.Api/Library/LibraryStructureService.cs
  11. 6 0
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  12. 15 11
      MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
  13. 27 12
      MediaBrowser.Providers/Movies/MovieDbProvider.cs
  14. 22 1
      MediaBrowser.Server.Implementations/Collections/CollectionImageProvider.cs
  15. 5 0
      MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
  16. 1 1
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  17. 0 1
      MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  18. 0 1
      MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  19. 0 5
      MediaBrowser.Server.Implementations/Persistence/BaseSqliteRepository.cs
  20. 24 17
      MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
  21. 19 2
      MediaBrowser.Server.Implementations/Photos/PhotoAlbumImageProvider.cs
  22. 5 2
      MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
  23. 5 2
      MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
  24. 11 2
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs

+ 4 - 0
Emby.Drawing/Emby.Drawing.csproj

@@ -75,6 +75,7 @@
     <Compile Include="ImageProcessor.cs" />
     <Compile Include="ImageMagick\PercentPlayedDrawer.cs" />
     <Compile Include="ImageMagick\PlayedIndicatorDrawer.cs" />
+    <Compile Include="NullImageEncoder.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="ImageMagick\UnplayedCountIndicator.cs" />
   </ItemGroup>
@@ -99,6 +100,9 @@
   <ItemGroup>
     <None Include="packages.config" />
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="GDI\empty.png" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 25 - 3
Emby.Drawing/GDI/GDIImageEncoder.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Logging;
 using System;
@@ -23,7 +22,20 @@ namespace Emby.Drawing.GDI
             _fileSystem = fileSystem;
             _logger = logger;
 
-            _logger.Info("GDI image processor initialized");
+            LogInfo();
+        }
+
+        private void LogInfo()
+        {
+            _logger.Info("GDIImageEncoder starting");
+            using (var stream = GetType().Assembly.GetManifestResourceStream(GetType().Namespace + ".empty.png"))
+            {
+                using (var img = Image.FromStream(stream))
+                {
+                    
+                }
+            }
+            _logger.Info("GDIImageEncoder started");
         }
 
         public string[] SupportedInputFormats
@@ -253,5 +265,15 @@ namespace Emby.Drawing.GDI
         {
             get { return "GDI"; }
         }
+
+        public bool SupportsImageCollageCreation
+        {
+            get { return true; }
+        }
+
+        public bool SupportsImageEncoding
+        {
+            get { return true; }
+        }
     }
 }

BIN
Emby.Drawing/GDI/empty.png


+ 12 - 6
Emby.Drawing/IImageEncoder.cs

@@ -17,12 +17,6 @@ namespace Emby.Drawing
         /// <value>The supported output formats.</value>
         ImageFormat[] SupportedOutputFormats { get; }
         /// <summary>
-        /// Gets the size of the image.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>ImageSize.</returns>
-        ImageSize GetImageSize(string path);
-        /// <summary>
         /// Crops the white space.
         /// </summary>
         /// <param name="inputPath">The input path.</param>
@@ -49,5 +43,17 @@ namespace Emby.Drawing
         /// </summary>
         /// <value>The name.</value>
         string Name { get; }
+
+        /// <summary>
+        /// Gets a value indicating whether [supports image collage creation].
+        /// </summary>
+        /// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value>
+        bool SupportsImageCollageCreation { get; }
+
+        /// <summary>
+        /// Gets a value indicating whether [supports image encoding].
+        /// </summary>
+        /// <value><c>true</c> if [supports image encoding]; otherwise, <c>false</c>.</value>
+        bool SupportsImageEncoding { get; }
     }
 }

+ 14 - 5
Emby.Drawing/ImageMagick/ImageMagickEncoder.cs

@@ -9,7 +9,6 @@ using System;
 using System.IO;
 using System.Linq;
 using CommonIO;
-using MediaBrowser.Common.IO;
 
 namespace Emby.Drawing.ImageMagick
 {
@@ -27,7 +26,7 @@ namespace Emby.Drawing.ImageMagick
             _httpClient = httpClient;
             _fileSystem = fileSystem;
 
-            LogImageMagickVersion();
+            LogVersion();
         }
 
         public string[] SupportedInputFormats
@@ -68,7 +67,7 @@ namespace Emby.Drawing.ImageMagick
             }
         }
 
-        private void LogImageMagickVersion()
+        private void LogVersion()
         {
             _logger.Info("ImageMagick version: " + Wand.VersionString);
             TestWebp();
@@ -88,9 +87,9 @@ namespace Emby.Drawing.ImageMagick
                     wand.SaveImage(tmpPath);
                 }
             }
-            catch (Exception ex)
+            catch 
             {
-                _logger.ErrorException("Error loading webp: ", ex);
+                //_logger.ErrorException("Error loading webp: ", ex);
                 _webpAvailable = false;
             }
         }
@@ -255,5 +254,15 @@ namespace Emby.Drawing.ImageMagick
                 throw new ObjectDisposedException(GetType().Name);
             }
         }
+
+        public bool SupportsImageCollageCreation
+        {
+            get { return true; }
+        }
+
+        public bool SupportsImageEncoding
+        {
+            get { return true; }
+        }
     }
 }

+ 2 - 2
Emby.Drawing/ImageMagick/StripCollageBuilder.cs

@@ -354,14 +354,14 @@ namespace Emby.Drawing.ImageMagick
 
         private MagickWand BuildSquareCollageWand(List<string> paths, int width, int height)
         {
-            var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
+            var inputPaths = ImageHelpers.ProjectPaths(paths, 3);
             using (var wandImages = new MagickWand(inputPaths.ToArray()))
             {
                 var wand = new MagickWand(width, height);
                 wand.OpenImage("gradient:#111111-#111111");
                 using (var draw = new DrawingWand())
                 {
-                    var iSlice = Convert.ToInt32(width * .225);
+                    var iSlice = Convert.ToInt32(width * .3);
                     int iTrans = Convert.ToInt32(height * .25);
                     int iHeight = Convert.ToInt32(height * .63);
                     var horizontalImagePadding = Convert.ToInt32(width * 0.02);

+ 20 - 1
Emby.Drawing/ImageProcessor.cs

@@ -110,6 +110,15 @@ namespace Emby.Drawing
             }
         }
 
+
+        public bool SupportsImageCollageCreation
+        {
+            get
+            {
+                return _imageEncoder.SupportsImageCollageCreation;
+            }
+        }
+
         private string ResizedImageCachePath
         {
             get
@@ -170,6 +179,11 @@ namespace Emby.Drawing
 
             var originalImagePath = originalImage.Path;
 
+            if (!_imageEncoder.SupportsImageEncoding)
+            {
+                return originalImagePath;
+            }
+
             if (options.HasDefaultOptions(originalImagePath) && options.Enhancers.Count == 0 && !options.CropWhiteSpace)
             {
                 // Just spit out the original file if all the options are default
@@ -178,7 +192,7 @@ namespace Emby.Drawing
 
             var dateModified = originalImage.DateModified;
 
-            if (options.CropWhiteSpace)
+            if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
             {
                 var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
 
@@ -295,6 +309,11 @@ namespace Emby.Drawing
 
                 _imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath);
             }
+            catch (NotImplementedException)
+            {
+                // No need to spam the log with an error message
+                return new Tuple<string, DateTime>(originalImagePath, dateModified);
+            }
             catch (Exception ex)
             {
                 // We have to have a catch-all here because some of the .net image methods throw a plain old Exception

+ 64 - 0
Emby.Drawing/NullImageEncoder.cs

@@ -0,0 +1,64 @@
+using System;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Model.Drawing;
+
+namespace Emby.Drawing
+{
+    public class NullImageEncoder : IImageEncoder
+    {
+        public string[] SupportedInputFormats
+        {
+            get
+            {
+                return new[]
+                {
+                    "png",
+                    "jpeg",
+                    "jpg"
+                };
+            }
+        }
+
+        public ImageFormat[] SupportedOutputFormats
+        {
+            get
+            {
+                return new[] { ImageFormat.Jpg, ImageFormat.Png };
+            }
+        }
+
+        public void CropWhiteSpace(string inputPath, string outputPath)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void EncodeImage(string inputPath, string outputPath, int width, int height, int quality, ImageProcessingOptions options)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void CreateImageCollage(ImageCollageOptions options)
+        {
+            throw new NotImplementedException();
+        }
+
+        public string Name
+        {
+            get { return "Null Image Encoder"; }
+        }
+
+        public bool SupportsImageCollageCreation
+        {
+            get { return false; }
+        }
+
+        public bool SupportsImageEncoding
+        {
+            get { return false; }
+        }
+
+        public void Dispose()
+        {
+        }
+    }
+}

+ 42 - 16
MediaBrowser.Api/Images/ImageService.cs

@@ -564,7 +564,14 @@ namespace MediaBrowser.Api.Images
 
             }).ToList() : new List<IImageEnhancer>();
 
-            var format = GetOutputFormat(request, imageInfo, supportedImageEnhancers);
+            var cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art;
+
+            if (request.CropWhitespace.HasValue)
+            {
+                cropwhitespace = request.CropWhitespace.Value;
+            }
+
+            var format = GetOutputFormat(request, imageInfo, cropwhitespace, supportedImageEnhancers);
             var contentType = GetMimeType(format, imageInfo.Path);
 
             var cacheGuid = new Guid(_imageProcessor.GetImageCacheTag(item, imageInfo, supportedImageEnhancers));
@@ -585,6 +592,7 @@ namespace MediaBrowser.Api.Images
             return GetImageResult(item,
                 request,
                 imageInfo,
+                cropwhitespace,
                 format,
                 supportedImageEnhancers,
                 contentType,
@@ -597,6 +605,7 @@ namespace MediaBrowser.Api.Images
         private async Task<object> GetImageResult(IHasImages item,
             ImageRequest request,
             ItemImageInfo image,
+            bool cropwhitespace,
             ImageFormat format,
             List<IImageEnhancer> enhancers,
             string contentType,
@@ -604,13 +613,6 @@ namespace MediaBrowser.Api.Images
             IDictionary<string, string> headers,
             bool isHeadRequest)
         {
-            var cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art;
-
-            if (request.CropWhitespace.HasValue)
-            {
-                cropwhitespace = request.CropWhitespace.Value;
-            }
-
             var options = new ImageProcessingOptions
             {
                 CropWhiteSpace = cropwhitespace,
@@ -644,7 +646,7 @@ namespace MediaBrowser.Api.Images
             });
         }
 
-        private ImageFormat GetOutputFormat(ImageRequest request, ItemImageInfo image, List<IImageEnhancer> enhancers)
+        private ImageFormat GetOutputFormat(ImageRequest request, ItemImageInfo image, bool cropwhitespace, List<IImageEnhancer> enhancers)
         {
             if (!string.IsNullOrWhiteSpace(request.Format))
             {
@@ -655,10 +657,37 @@ namespace MediaBrowser.Api.Images
                 }
             }
 
+            var extension = Path.GetExtension(image.Path);
+            ImageFormat? inputFormat = null;
+
+            if (string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
+            {
+                inputFormat = ImageFormat.Jpg;
+            }
+            else if (string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase))
+            {
+                inputFormat = ImageFormat.Png;
+            }
+
+            var clientSupportedFormats = GetClientSupportedFormats();
+            if (inputFormat.HasValue && clientSupportedFormats.Contains(inputFormat.Value) && enhancers.Count == 0)
+            {
+                if ((request.Quality ?? 100) == 100 && !request.Height.HasValue && !request.Width.HasValue &&
+                    !request.AddPlayedIndicator && !request.PercentPlayed.HasValue && !request.UnplayedCount.HasValue && string.IsNullOrWhiteSpace(request.BackgroundColor))
+                {
+                    // TODO: Allow this when specfying max width/height if the value is in range
+                    if (!cropwhitespace && !request.MaxHeight.HasValue && !request.MaxWidth.HasValue)
+                    {
+                        return inputFormat.Value;
+                    }
+                }
+            }
+
             var serverFormats = _imageProcessor.GetSupportedImageOutputFormats();
 
-            if (serverFormats.Contains(ImageFormat.Webp) &&
-                GetClientSupportedFormats().Contains(ImageFormat.Webp))
+            // Client doesn't care about format, so start with webp if supported
+            if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp))
             {
                 return ImageFormat.Webp;
             }
@@ -668,10 +697,7 @@ namespace MediaBrowser.Api.Images
                 return ImageFormat.Png;
             }
 
-            var extension = Path.GetExtension(image.Path);
-
-            if (string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) ||
-                string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
+            if (inputFormat.HasValue && inputFormat.Value == ImageFormat.Jpg)
             {
                 return ImageFormat.Jpg;
             }
@@ -682,7 +708,7 @@ namespace MediaBrowser.Api.Images
 
         private ImageFormat[] GetClientSupportedFormats()
         {
-            var supportsWebP = (Request.AcceptTypes ?? new string[] {}).Contains("image/webp", StringComparer.OrdinalIgnoreCase);
+            var supportsWebP = (Request.AcceptTypes ?? new string[] { }).Contains("image/webp", StringComparer.OrdinalIgnoreCase);
 
             var userAgent = Request.UserAgent ?? string.Empty;
 

+ 10 - 6
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -52,7 +52,7 @@ namespace MediaBrowser.Api.Library
         /// Gets or sets the path.
         /// </summary>
         /// <value>The path.</value>
-        public string Path { get; set; }
+        public string[] Paths { get; set; }
     }
 
     [Route("/Library/VirtualFolders", "DELETE")]
@@ -207,11 +207,12 @@ namespace MediaBrowser.Api.Library
                 throw new ArgumentException("There is already a media library with the name " + name + ".");
             }
 
-            if (!string.IsNullOrWhiteSpace(request.Path))
+            if (request.Paths != null)
             {
-                if (!_fileSystem.DirectoryExists(request.Path))
+                var invalidpath = request.Paths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i));
+                if (invalidpath != null)
                 {
-                    throw new DirectoryNotFoundException("The specified folder does not exist.");
+                    throw new ArgumentException("The specified path does not exist: " + invalidpath + ".");
                 }
             }
             
@@ -231,9 +232,12 @@ namespace MediaBrowser.Api.Library
                     }
                 }
 
-                if (!string.IsNullOrWhiteSpace(request.Path))
+                if (request.Paths != null)
                 {
-                    LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, _appPaths);
+                    foreach (var path in request.Paths)
+                    {
+                        LibraryHelpers.AddMediaPath(_fileSystem, request.Name, path, _appPaths);
+                    }
                 }
             }
             finally

+ 6 - 0
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -105,5 +105,11 @@ namespace MediaBrowser.Controller.Drawing
         /// </summary>
         /// <param name="options">The options.</param>
         Task CreateImageCollage(ImageCollageOptions options);
+
+        /// <summary>
+        /// Gets a value indicating whether [supports image collage creation].
+        /// </summary>
+        /// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value>
+        bool SupportsImageCollageCreation { get; }
     }
 }

+ 15 - 11
MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs

@@ -87,22 +87,26 @@ namespace MediaBrowser.Providers.Movies
             if (string.IsNullOrEmpty(tmdbId))
             {
                 movieInfo = await MovieDbProvider.Current.FetchMainResult(imdbId, false, language, cancellationToken).ConfigureAwait(false);
-                if (movieInfo == null) return item;
-
-                tmdbId = movieInfo.id.ToString(_usCulture);
+                if (movieInfo != null)
+                {
+                    tmdbId = movieInfo.id.ToString(_usCulture);
 
-                dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
-				_fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-                _jsonSerializer.SerializeToFile(movieInfo, dataFilePath);
+                    dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
+                    _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath));
+                    _jsonSerializer.SerializeToFile(movieInfo, dataFilePath);
+                }
             }
 
-            await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
+            if (!string.IsNullOrWhiteSpace(tmdbId))
+            {
+                await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
 
-            dataFilePath = dataFilePath ?? MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
-            movieInfo = movieInfo ?? _jsonSerializer.DeserializeFromFile<MovieDbProvider.CompleteMovieData>(dataFilePath);
+                dataFilePath = dataFilePath ?? MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
+                movieInfo = movieInfo ?? _jsonSerializer.DeserializeFromFile<MovieDbProvider.CompleteMovieData>(dataFilePath);
 
-            ProcessMainInfo(item, preferredCountryCode, movieInfo);
-            item.HasMetadata = true;
+                ProcessMainInfo(item, preferredCountryCode, movieInfo);
+                item.HasMetadata = true;
+            }
 
             return item;
         }

+ 27 - 12
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -16,9 +16,11 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
+using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
+using MediaBrowser.Model.Net;
 
 namespace MediaBrowser.Providers.Movies
 {
@@ -116,7 +118,7 @@ namespace MediaBrowser.Providers.Movies
         public Task<MetadataResult<T>> GetItemMetadata<T>(ItemLookupInfo id, CancellationToken cancellationToken)
             where T : BaseItem, new()
         {
-			var movieDb = new GenericMovieDbInfo<T>(_logger, _jsonSerializer, _libraryManager, _fileSystem);
+            var movieDb = new GenericMovieDbInfo<T>(_logger, _jsonSerializer, _libraryManager, _fileSystem);
 
             return movieDb.GetMetadata(id, cancellationToken);
         }
@@ -211,7 +213,7 @@ namespace MediaBrowser.Providers.Movies
 
             var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage);
 
-			_fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath));
+            _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath));
 
             _jsonSerializer.SerializeToFile(mainResult, dataFilePath);
         }
@@ -309,25 +311,38 @@ namespace MediaBrowser.Providers.Movies
             var cacheMode = isTmdbId ? CacheMode.None : CacheMode.Unconditional;
             var cacheLength = TimeSpan.FromDays(3);
 
-            using (var json = await GetMovieDbResponse(new HttpRequestOptions
+            try
             {
-                Url = url,
-                CancellationToken = cancellationToken,
-                AcceptHeader = AcceptHeader,
-                CacheMode = cacheMode,
-                CacheLength = cacheLength
+                using (var json = await GetMovieDbResponse(new HttpRequestOptions
+                {
+                    Url = url,
+                    CancellationToken = cancellationToken,
+                    AcceptHeader = AcceptHeader,
+                    CacheMode = cacheMode,
+                    CacheLength = cacheLength
 
-            }).ConfigureAwait(false))
+                }).ConfigureAwait(false))
+                {
+                    mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
+                }
+            }
+            catch (HttpException ex)
             {
-                mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
+                // Return null so that callers know there is no metadata for this id
+                if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
+                {
+                    return null;
+                }
+
+                throw;
             }
 
             cancellationToken.ThrowIfCancellationRequested();
 
             // If the language preference isn't english, then have the overview fallback to english if it's blank
             if (mainResult != null &&
-                string.IsNullOrEmpty(mainResult.overview) && 
-                !string.IsNullOrEmpty(language) && 
+                string.IsNullOrEmpty(mainResult.overview) &&
+                !string.IsNullOrEmpty(language) &&
                 !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
             {
                 _logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");

+ 22 - 1
MediaBrowser.Server.Implementations/Collections/CollectionImageProvider.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Server.Implementations.Photos;
 using MoreLinq;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 using CommonIO;
@@ -74,7 +75,27 @@ namespace MediaBrowser.Server.Implementations.Collections
                 .DistinctBy(i => i.Id)
                 .ToList();
 
-            return Task.FromResult(GetFinalItems(items));
+            return Task.FromResult(GetFinalItems(items, 2));
+        }
+
+        protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+        {
+            var image = itemsWithImages
+                .Where(i => i.HasImage(ImageType.Primary) && i.GetImageInfo(ImageType.Primary, 0).IsLocalFile && Path.HasExtension(i.GetImagePath(ImageType.Primary)))
+                .Select(i => i.GetImagePath(ImageType.Primary))
+                .FirstOrDefault();
+
+            if (string.IsNullOrWhiteSpace(image))
+            {
+                return null;
+            }
+
+            var ext = Path.GetExtension(image);
+
+            var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ext);
+            File.Copy(image, outputPath);
+
+            return outputPath;
         }
     }
 }

+ 5 - 0
MediaBrowser.Server.Implementations/Devices/DeviceManager.cs

@@ -265,6 +265,11 @@ namespace MediaBrowser.Server.Implementations.Devices
                 return true;
             }
 
+            if (policy.IsAdministrator)
+            {
+                return true;
+            }
+
             return ListHelper.ContainsIgnoreCase(policy.EnabledDevices, id);
         }
     }

+ 1 - 1
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -88,7 +88,7 @@ namespace MediaBrowser.Server.Implementations.IO
             // This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to.
             // Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds
             // But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
-            await Task.Delay(20000).ConfigureAwait(false);
+            await Task.Delay(25000).ConfigureAwait(false);
 
             string val;
             _tempIgnoredPaths.TryRemove(path, out val);

+ 0 - 1
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -675,7 +675,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                     Name = info.Name,
                     EpisodeTitle = info.EpisodeTitle,
                     ProgramId = info.Id,
-                    HasImage = info.HasImage,
                     ImagePath = info.ImagePath,
                     ImageUrl = info.ImageUrl,
                     OriginalAirDate = info.OriginalAirDate,

+ 0 - 1
MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs

@@ -333,7 +333,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
                 IsRepeat = repeat,
                 IsSeries = showType.IndexOf("series", StringComparison.OrdinalIgnoreCase) != -1,
                 ImageUrl = imageUrl,
-                HasImage = details.hasImageArtwork,
                 IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
                 IsSports = showType.IndexOf("sports", StringComparison.OrdinalIgnoreCase) != -1,
                 IsMovie = showType.IndexOf("movie", StringComparison.OrdinalIgnoreCase) != -1 || showType.IndexOf("film", StringComparison.OrdinalIgnoreCase) != -1,

+ 0 - 5
MediaBrowser.Server.Implementations/Persistence/BaseSqliteRepository.cs

@@ -46,11 +46,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
         }
 
-        protected virtual void DisposeInternal()
-        {
-
-        }
-
         protected abstract void CloseConnection();
     }
 }

+ 24 - 17
MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs

@@ -92,11 +92,11 @@ namespace MediaBrowser.Server.Implementations.Photos
             string cacheKey,
             CancellationToken cancellationToken)
         {
-            var outputPath = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid() + ".png");
-            FileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
-            var imageCreated = await CreateImage(item, itemsWithImages, outputPath, imageType, 0).ConfigureAwait(false);
+            var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N"));
+            FileSystem.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension));
+            string outputPath = await CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0).ConfigureAwait(false);
 
-            if (!imageCreated)
+            if (string.IsNullOrWhiteSpace(outputPath))
             {
                 return ItemUpdateType.None;
             }
@@ -117,7 +117,7 @@ namespace MediaBrowser.Server.Implementations.Photos
             return parts.GetMD5().ToString("N");
         }
 
-        protected Task<bool> CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
+        protected Task<string> CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
         {
             return CreateCollage(primaryItem, items, outputPath, 640, 360);
         }
@@ -144,22 +144,22 @@ namespace MediaBrowser.Server.Implementations.Photos
                 .Where(i => !string.IsNullOrWhiteSpace(i));
         }
 
-        protected Task<bool> CreatePosterCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
+        protected Task<string> CreatePosterCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
         {
             return CreateCollage(primaryItem, items, outputPath, 400, 600);
         }
 
-        protected Task<bool> CreateSquareCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
+        protected Task<string> CreateSquareCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
         {
             return CreateCollage(primaryItem, items, outputPath, 600, 600);
         }
 
-        protected Task<bool> CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
+        protected Task<string> CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
         {
             return CreateCollage(primaryItem, items, outputPath, width, height);
         }
 
-        private Task<bool> CreateCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
+        private async Task<string> CreateCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
         {
             FileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
 
@@ -173,11 +173,16 @@ namespace MediaBrowser.Server.Implementations.Photos
 
             if (options.InputPaths.Length == 0)
             {
-                return Task.FromResult(false);
+                return null;
+            }
+
+            if (!ImageProcessor.SupportsImageCollageCreation)
+            {
+                return null;
             }
 
-            ImageProcessor.CreateImageCollage(options);
-            return Task.FromResult(true);
+            await ImageProcessor.CreateImageCollage(options).ConfigureAwait(false);
+            return outputPath;
         }
 
         public string Name
@@ -185,17 +190,19 @@ namespace MediaBrowser.Server.Implementations.Photos
             get { return "Dynamic Image Provider"; }
         }
 
-        protected virtual async Task<bool> CreateImage(IHasImages item,
+        protected virtual async Task<string> CreateImage(IHasImages item,
             List<BaseItem> itemsWithImages,
-            string outputPath,
+            string outputPathWithoutExtension,
             ImageType imageType,
             int imageIndex)
         {
             if (itemsWithImages.Count == 0)
             {
-                return false;
+                return null;
             }
 
+            string outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
+
             if (imageType == ImageType.Thumb)
             {
                 return await CreateThumbCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
@@ -207,7 +214,7 @@ namespace MediaBrowser.Server.Implementations.Photos
                 {
                     return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
                 }
-                if (item is PhotoAlbum || item is Playlist)
+                if (item is Playlist)
                 {
                     return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
                 }
@@ -320,7 +327,7 @@ namespace MediaBrowser.Server.Implementations.Photos
             var random = DateTime.Now.DayOfYear % MaxImageAgeDays;
 
             return items
-                .OrderBy(i => (random + "" + items.IndexOf(i)).GetMD5())
+                .OrderBy(i => (random + string.Empty + items.IndexOf(i)).GetMD5())
                 .Take(limit)
                 .OrderBy(i => i.Name)
                 .ToList();

+ 19 - 2
MediaBrowser.Server.Implementations/Photos/PhotoAlbumImageProvider.cs

@@ -1,9 +1,9 @@
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 using CommonIO;
@@ -20,9 +20,26 @@ namespace MediaBrowser.Server.Implementations.Photos
         protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
         {
             var photoAlbum = (PhotoAlbum)item;
-            var items = GetFinalItems(photoAlbum.Children.ToList());
+            var items = GetFinalItems(photoAlbum.Children.ToList(), 1);
 
             return Task.FromResult(items);
         }
+
+        protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, Model.Entities.ImageType imageType, int imageIndex)
+        {
+            var photoFile = itemsWithImages.Where(i => Path.HasExtension(i.Path)).Select(i => i.Path).FirstOrDefault();
+
+            if (string.IsNullOrWhiteSpace(photoFile))
+            {
+                return null;
+            }
+
+            var ext = Path.GetExtension(photoFile);
+
+            var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ext);
+            File.Copy(photoFile, outputPath);
+
+            return outputPath;
+        }
     }
 }

+ 5 - 2
MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Server.Implementations.Photos;
 using MoreLinq;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 using CommonIO;
@@ -97,13 +98,15 @@ namespace MediaBrowser.Server.Implementations.UserViews
             return item is CollectionFolder;
         }
 
-        protected override async Task<bool> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPath, ImageType imageType, int imageIndex)
+        protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
         {
+            var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
+
             if (imageType == ImageType.Primary)
             {
                 if (itemsWithImages.Count == 0)
                 {
-                    return false;
+                    return null;
                 }
 
                 return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false);

+ 5 - 2
MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs

@@ -11,6 +11,7 @@ using MediaBrowser.Server.Implementations.Photos;
 using MoreLinq;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 using CommonIO;
@@ -161,14 +162,16 @@ namespace MediaBrowser.Server.Implementations.UserViews
             return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty);
         }
 
-        protected override async Task<bool> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPath, ImageType imageType, int imageIndex)
+        protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
         {
+            var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
+
             var view = (UserView)item;
             if (imageType == ImageType.Primary && IsUsingCollectionStrip(view))
             {
                 if (itemsWithImages.Count == 0)
                 {
-                    return false;
+                    return null;
                 }
 
                 return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false);

+ 11 - 2
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -577,11 +577,20 @@ namespace MediaBrowser.Server.Startup.Common
                 }
                 catch (Exception ex)
                 {
-                    Logger.ErrorException("Error loading ImageMagick. Will revert to GDI.", ex);
+                    Logger.Error("Error loading ImageMagick. Will revert to GDI.");
                 }
             }
 
-            return new GDIImageEncoder(FileSystemManager, LogManager.GetLogger("GDI"));
+            try
+            {
+                return new GDIImageEncoder(FileSystemManager, LogManager.GetLogger("GDI"));
+            }
+            catch (Exception ex)
+            {
+                Logger.Error("Error loading GDI. Will revert to NullImageEncoder.");
+            }
+
+            return new NullImageEncoder();
         }
 
         protected override INetworkManager CreateNetworkManager(ILogger logger)