New: Swap to ImageSharp library for resizing posters
This commit is contained in:
parent
f2efebf7d9
commit
3ac3dd3ca5
18
build.sh
18
build.sh
|
@ -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…
Reference in New Issue