|
|
|
@ -3,6 +3,7 @@ using MediaBrowser.Common.IO;
|
|
|
|
|
using MediaBrowser.Controller;
|
|
|
|
|
using MediaBrowser.Controller.Drawing;
|
|
|
|
|
using MediaBrowser.Controller.Entities;
|
|
|
|
|
using MediaBrowser.Controller.MediaEncoding;
|
|
|
|
|
using MediaBrowser.Controller.Providers;
|
|
|
|
|
using MediaBrowser.Model.Drawing;
|
|
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
@ -52,12 +53,14 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|
|
|
|
private readonly IFileSystem _fileSystem;
|
|
|
|
|
private readonly IJsonSerializer _jsonSerializer;
|
|
|
|
|
private readonly IServerApplicationPaths _appPaths;
|
|
|
|
|
private readonly IMediaEncoder _mediaEncoder;
|
|
|
|
|
|
|
|
|
|
public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer)
|
|
|
|
|
public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
|
|
|
|
|
{
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_fileSystem = fileSystem;
|
|
|
|
|
_jsonSerializer = jsonSerializer;
|
|
|
|
|
_mediaEncoder = mediaEncoder;
|
|
|
|
|
_appPaths = appPaths;
|
|
|
|
|
|
|
|
|
|
_saveImageSizeTimer = new Timer(SaveImageSizeCallback, null, Timeout.Infinite, Timeout.Infinite);
|
|
|
|
@ -66,7 +69,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sizeDictionary = jsonSerializer.DeserializeFromFile<Dictionary<Guid, ImageSize>>(ImageSizeFile) ??
|
|
|
|
|
sizeDictionary = jsonSerializer.DeserializeFromFile<Dictionary<Guid, ImageSize>>(ImageSizeFile) ??
|
|
|
|
|
new Dictionary<Guid, ImageSize>();
|
|
|
|
|
}
|
|
|
|
|
catch (FileNotFoundException)
|
|
|
|
@ -213,6 +216,39 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed.HasValue;
|
|
|
|
|
|
|
|
|
|
if (!hasPostProcessing)
|
|
|
|
|
{
|
|
|
|
|
using (var outputStream = await _mediaEncoder.EncodeImage(new ImageEncodingOptions
|
|
|
|
|
{
|
|
|
|
|
InputPath = originalImagePath,
|
|
|
|
|
MaxHeight = options.MaxHeight,
|
|
|
|
|
MaxWidth = options.MaxWidth,
|
|
|
|
|
Height = options.Height,
|
|
|
|
|
Width = options.Width,
|
|
|
|
|
Quality = options.Quality,
|
|
|
|
|
Format = options.OutputFormat == ImageOutputFormat.Original ? Path.GetExtension(originalImagePath).TrimStart('.') : options.OutputFormat.ToString().ToLower()
|
|
|
|
|
|
|
|
|
|
}, CancellationToken.None).ConfigureAwait(false))
|
|
|
|
|
{
|
|
|
|
|
using (var outputMemoryStream = new MemoryStream())
|
|
|
|
|
{
|
|
|
|
|
// Save to the memory stream
|
|
|
|
|
await outputStream.CopyToAsync(outputMemoryStream).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
var bytes = outputMemoryStream.ToArray();
|
|
|
|
|
|
|
|
|
|
await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
// kick off a task to cache the result
|
|
|
|
|
await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
|
|
|
|
|
{
|
|
|
|
|
// Copy to memory stream to avoid Image locking file
|
|
|
|
@ -241,8 +277,8 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|
|
|
|
thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
|
|
|
|
|
thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
|
|
|
|
thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
|
|
|
|
thumbnailGraph.CompositingMode = string.IsNullOrEmpty(options.BackgroundColor) && !options.UnplayedCount.HasValue && !options.AddPlayedIndicator && !options.PercentPlayed.HasValue ?
|
|
|
|
|
CompositingMode.SourceCopy :
|
|
|
|
|
thumbnailGraph.CompositingMode = !hasPostProcessing ?
|
|
|
|
|
CompositingMode.SourceCopy :
|
|
|
|
|
CompositingMode.SourceOver;
|
|
|
|
|
|
|
|
|
|
SetBackgroundColor(thumbnailGraph, options);
|
|
|
|
@ -263,7 +299,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|
|
|
|
await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
// kick off a task to cache the result
|
|
|
|
|
CacheResizedImage(cacheFilePath, bytes, semaphore);
|
|
|
|
|
await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -272,11 +308,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
semaphore.Release();
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -285,33 +319,26 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="cacheFilePath">The cache file path.</param>
|
|
|
|
|
/// <param name="bytes">The bytes.</param>
|
|
|
|
|
/// <param name="semaphore">The semaphore.</param>
|
|
|
|
|
private void CacheResizedImage(string cacheFilePath, byte[] bytes, SemaphoreSlim semaphore)
|
|
|
|
|
/// <returns>Task.</returns>
|
|
|
|
|
private async Task CacheResizedImage(string cacheFilePath, byte[] bytes)
|
|
|
|
|
{
|
|
|
|
|
Task.Run(async () =>
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var parentPath = Path.GetDirectoryName(cacheFilePath);
|
|
|
|
|
var parentPath = Path.GetDirectoryName(cacheFilePath);
|
|
|
|
|
|
|
|
|
|
Directory.CreateDirectory(parentPath);
|
|
|
|
|
Directory.CreateDirectory(parentPath);
|
|
|
|
|
|
|
|
|
|
// Save to the cache location
|
|
|
|
|
using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
|
|
|
|
|
{
|
|
|
|
|
// Save to the filestream
|
|
|
|
|
await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.ErrorException("Error writing to image cache file {0}", ex, cacheFilePath);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
// Save to the cache location
|
|
|
|
|
using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
|
|
|
|
|
{
|
|
|
|
|
semaphore.Release();
|
|
|
|
|
// Save to the filestream
|
|
|
|
|
await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.ErrorException("Error writing to image cache file {0}", ex, cacheFilePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -519,7 +546,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|
|
|
|
{
|
|
|
|
|
filename += "iv=" + IndicatorVersion;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(backgroundColor))
|
|
|
|
|
{
|
|
|
|
|
filename += "b=" + backgroundColor;
|
|
|
|
|