New: Swap to ImageSharp library for resizing posters

pull/3357/head
ta264 5 years ago committed by Taloth Saldono
parent f2efebf7d9
commit 3ac3dd3ca5

@ -192,6 +192,24 @@ PatchMono()
fi fi
done done
# Copy more stable version of Vectors for mono <5.12
if [ -e $path/System.Numerics.Vectors.dll ]; then
packageDir="$HOME/.nuget/packages/system.numerics.vectors/4.5.0"
if [ ! -d "$HOME/.nuget/packages/system.numerics.vectors/4.5.0" ]; then
# May reside in the NuGetFallback folder, which is harder to find
# Download somewhere to get the real cache populated
if [ $runtime = "dotnet" ] ; then
$nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
else
mono $nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
fi
rm -rf ./_temp/System.Numerics.Vectors
fi
# Copy the netstandard2.0 version rather than net46
cp "$packageDir/lib/netstandard2.0/System.Numerics.Vectors.dll" $path/
fi
} }
PackageMono() PackageMono()

@ -4,7 +4,9 @@ using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using SixLabors.ImageSharp;
namespace NzbDrone.Core.Test.MediaCoverTests namespace NzbDrone.Core.Test.MediaCoverTests
{ {
@ -14,13 +16,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
Mocker.GetMock<IDiskProvider>() if (PlatformInfo.GetVersion() < new Version(5, 8))
.Setup(v => v.OpenReadStream(It.IsAny<string>())) {
.Returns<string>(s => new FileStream(s, FileMode.Open)); Assert.Inconclusive("Not supported on Mono < 5.8");
}
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.OpenWriteStream(It.IsAny<string>()))
.Returns<string>(s => new FileStream(s, FileMode.Create));
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(It.IsAny<string>())) .Setup(v => v.FileExists(It.IsAny<string>()))
@ -29,6 +28,8 @@ namespace NzbDrone.Core.Test.MediaCoverTests
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Setup(v => v.DeleteFile(It.IsAny<string>())) .Setup(v => v.DeleteFile(It.IsAny<string>()))
.Callback<string>(s => File.Delete(s)); .Callback<string>(s => File.Delete(s));
Mocker.SetConstant<IPlatformInfo>(Mocker.Resolve<PlatformInfo>());
} }
[Test] [Test]
@ -45,9 +46,11 @@ namespace NzbDrone.Core.Test.MediaCoverTests
fileInfo.Exists.Should().BeTrue(); fileInfo.Exists.Should().BeTrue();
fileInfo.Length.Should().BeInRange(1000, 30000); fileInfo.Length.Should().BeInRange(1000, 30000);
var image = System.Drawing.Image.FromFile(resizedFile); using (var image = Image.Load(resizedFile))
image.Height.Should().Be(170); {
image.Width.Should().Be(170); image.Height.Should().Be(170);
image.Width.Should().Be(170);
}
} }
[Test] [Test]

@ -1,44 +0,0 @@
using System;
using System.Drawing;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.MediaCover
{
public static class GdiPlusInterop
{
private static Exception _gdiPlusException;
static GdiPlusInterop()
{
TestLibrary();
}
private static void TestLibrary()
{
if (OsInfo.IsWindows)
{
return;
}
try
{
// We use StringFormat as test coz it gets properly cleaned up by the finalizer even if gdiplus is absent and is relatively non-invasive.
var strFormat = new StringFormat();
strFormat.Dispose();
}
catch (Exception ex)
{
_gdiPlusException = ex;
}
}
public static void CheckGdiPlus()
{
if (_gdiPlusException != null)
{
throw new DllNotFoundException("Couldn't load GDIPlus library", _gdiPlusException);
}
}
}
}

@ -1,5 +1,9 @@
using ImageResizer; using NzbDrone.Common.Disk;
using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
using SixLabors.Memory;
namespace NzbDrone.Core.MediaCover namespace NzbDrone.Core.MediaCover
{ {
@ -11,29 +15,40 @@ namespace NzbDrone.Core.MediaCover
public class ImageResizer : IImageResizer public class ImageResizer : IImageResizer
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly bool _enabled;
public ImageResizer(IDiskProvider diskProvider) public ImageResizer(IDiskProvider diskProvider, IPlatformInfo platformInfo)
{ {
_diskProvider = diskProvider; _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) public void Resize(string source, string destination, int height)
{ {
if (!_enabled) return;
try try
{ {
GdiPlusInterop.CheckGdiPlus(); using (var image = Image.Load(source))
using (var sourceStream = _diskProvider.OpenReadStream(source))
{ {
using (var outputStream = _diskProvider.OpenWriteStream(destination)) image.Mutate(x => x.Resize(0, height));
{ image.Save(destination);
var settings = new Instructions();
settings.Height = height;
var job = new ImageJob(sourceStream, outputStream, settings);
ImageBuilder.Current.Build(job);
}
} }
} }
catch catch

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -35,6 +36,10 @@ namespace NzbDrone.Core.MediaCover
private readonly string _coverRootFolder; 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, public MediaCoverService(IImageResizer resizer,
IHttpClient httpClient, IHttpClient httpClient,
IDiskProvider diskProvider, IDiskProvider diskProvider,
@ -85,6 +90,8 @@ namespace NzbDrone.Core.MediaCover
private void EnsureCovers(Series series) private void EnsureCovers(Series series)
{ {
var toResize = new List<Tuple<MediaCover, bool>>();
foreach (var cover in series.Images) foreach (var cover in series.Images)
{ {
var fileName = GetCoverPath(series.Id, cover.CoverType); var fileName = GetCoverPath(series.Id, cover.CoverType);
@ -106,7 +113,21 @@ namespace NzbDrone.Core.MediaCover
_logger.Error(e, "Couldn't download media cover for {0}", series); _logger.Error(e, "Couldn't download media cover for {0}", series);
} }
EnsureResizedCovers(series, cover, !alreadyExists); toResize.Add(Tuple.Create(cover, alreadyExists));
}
try
{
_semaphore.Wait();
foreach (var tuple in toResize)
{
EnsureResizedCovers(series, tuple.Item1, !tuple.Item2);
}
}
finally
{
_semaphore.Release();
} }
} }

@ -6,7 +6,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentMigrator.Runner" Version="1.6.2" /> <PackageReference Include="FluentMigrator.Runner" Version="1.6.2" />
<PackageReference Include="FluentValidation" Version="8.4.0" /> <PackageReference Include="FluentValidation" Version="8.4.0" />
<PackageReference Include="ImageResizer" Version="4.2.5" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0007" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="NLog" Version="4.6.6" /> <PackageReference Include="NLog" Version="4.6.6" />
<PackageReference Include="OAuth" Version="1.0.3" /> <PackageReference Include="OAuth" Version="1.0.3" />
@ -44,4 +45,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -76,7 +76,7 @@ namespace NzbDrone.Test.Common
return; return;
} }
Console.WriteLine("Waiting for NzbDrone to start. Response Status : {0} [{1}] {2}", statusCall.ResponseStatus, statusCall.StatusDescription, statusCall.ErrorException); Console.WriteLine("Waiting for NzbDrone to start. Response Status : {0} [{1}] {2}", statusCall.ResponseStatus, statusCall.StatusDescription, statusCall.ErrorException.Message);
Thread.Sleep(500); Thread.Sleep(500);
} }

Loading…
Cancel
Save