diff --git a/src/NzbDrone.Core.Test/MediaCoverTests/ImageResizerFixture.cs b/src/NzbDrone.Core.Test/MediaCoverTests/ImageResizerFixture.cs index a7d9639f7..fcec07b32 100644 --- a/src/NzbDrone.Core.Test/MediaCoverTests/ImageResizerFixture.cs +++ b/src/NzbDrone.Core.Test/MediaCoverTests/ImageResizerFixture.cs @@ -4,7 +4,9 @@ using Moq; using NUnit.Framework; using NzbDrone.Common.Disk; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Test.Framework; +using SixLabors.ImageSharp; namespace NzbDrone.Core.Test.MediaCoverTests { @@ -14,13 +16,10 @@ public class ImageResizerFixture : CoreTest [SetUp] public void SetUp() { - Mocker.GetMock() - .Setup(v => v.OpenReadStream(It.IsAny())) - .Returns(s => new FileStream(s, FileMode.Open)); - - Mocker.GetMock() - .Setup(v => v.OpenWriteStream(It.IsAny())) - .Returns(s => new FileStream(s, FileMode.Create)); + if (PlatformInfo.IsMono && PlatformInfo.GetVersion() < new Version(5, 8)) + { + Assert.Inconclusive("Not supported on Mono < 5.8"); + } Mocker.GetMock() .Setup(v => v.FileExists(It.IsAny())) @@ -29,10 +28,8 @@ public void SetUp() Mocker.GetMock() .Setup(v => v.DeleteFile(It.IsAny())) .Callback(s => File.Delete(s)); - - Mocker.GetMock() - .Setup(v => v.CanUseGDIPlus()) - .Returns(true); + + Mocker.SetConstant(Mocker.Resolve()); } [Test] @@ -49,9 +46,11 @@ public void should_resize_image() fileInfo.Exists.Should().BeTrue(); fileInfo.Length.Should().BeInRange(1000, 30000); - var image = System.Drawing.Image.FromFile(resizedFile); - image.Height.Should().Be(170); - image.Width.Should().Be(170); + using (var image = Image.Load(resizedFile)) + { + image.Height.Should().Be(170); + image.Width.Should().Be(170); + } } [Test] diff --git a/src/NzbDrone.Core/MediaCover/ImageResizer.cs b/src/NzbDrone.Core/MediaCover/ImageResizer.cs index 63a202c59..42f64092a 100644 --- a/src/NzbDrone.Core/MediaCover/ImageResizer.cs +++ b/src/NzbDrone.Core/MediaCover/ImageResizer.cs @@ -1,6 +1,8 @@ using System; using NzbDrone.Common.Disk; +using NzbDrone.Common.EnvironmentInfo; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; using SixLabors.Memory; @@ -14,23 +16,39 @@ public interface IImageResizer public class ImageResizer : IImageResizer { private readonly IDiskProvider _diskProvider; + private readonly bool _enabled; - public ImageResizer(IDiskProvider diskProvider) + public ImageResizer(IDiskProvider diskProvider, IPlatformInfo platformInfo) { _diskProvider = diskProvider; + // Random segfaults on mono 5.0 and 5.4 + if (PlatformInfo.IsMono && platformInfo.Version < new System.Version(5, 8)) + { + return; + } + + _enabled = true; + // More conservative memory allocation SixLabors.ImageSharp.Configuration.Default.MemoryAllocator = new SimpleGcMemoryAllocator(); + + // Thumbnails don't need super high quality + SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder + { + Quality = 92 + }); } public void Resize(string source, string destination, int height) { + if (!_enabled) return; + try { using (var image = Image.Load(source)) { - var width = (int)Math.Floor((double)image.Width * (double)height / (double)image.Height); - image.Mutate(x => x.Resize(width, height)); + image.Mutate(x => x.Resize(0, height)); image.Save(destination); } } diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index 6ce0f1ac6..60ff480d0 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Net; +using System.Threading; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; @@ -40,6 +41,11 @@ public class MediaCoverService : private readonly string _coverRootFolder; + // ImageSharp is slow on ARM (no hardware acceleration on mono yet) + // So limit the number of concurrent resizing tasks + private static SemaphoreSlim _semaphore = new SemaphoreSlim((int)Math.Ceiling(Environment.ProcessorCount / 2.0)); + + public MediaCoverService(IImageResizer resizer, IHttpClient httpClient, IDiskProvider diskProvider, @@ -94,6 +100,8 @@ private string GetMovieCoverPath(int movieId) private void EnsureCovers(Movie movie, int retried = 0) { + var toResize = new List>(); + foreach (var cover in movie.Images) { var fileName = GetCoverPath(movie.Id, cover.CoverType); @@ -133,7 +141,21 @@ private void EnsureCovers(Movie movie, int retried = 0) _logger.Error(e, "Couldn't download media cover for {0}", movie); } - EnsureResizedCovers(movie, cover, !alreadyExists); + toResize.Add(Tuple.Create(cover, alreadyExists)); + } + + try + { + _semaphore.Wait(); + + foreach (var tuple in toResize) + { + EnsureResizedCovers(movie, tuple.Item1, !tuple.Item2); + } + } + finally + { + _semaphore.Release(); } } diff --git a/src/NzbDrone.Test.Common/NzbDroneRunner.cs b/src/NzbDrone.Test.Common/NzbDroneRunner.cs index add19f5f2..c4741c1c0 100644 --- a/src/NzbDrone.Test.Common/NzbDroneRunner.cs +++ b/src/NzbDrone.Test.Common/NzbDroneRunner.cs @@ -68,7 +68,7 @@ public void Start() return; } - Console.WriteLine("Waiting for Radarr to start. Response Status : {0} [{1}] {2}", statusCall.ResponseStatus, statusCall.StatusDescription, statusCall.ErrorException); + Console.WriteLine("Waiting for Radarr to start. Response Status : {0} [{1}] {2}", statusCall.ResponseStatus, statusCall.StatusDescription, statusCall.ErrorException.Message); Thread.Sleep(500); }