aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/ImageEncoder.cs65
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs41
-rw-r--r--MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs87
-rw-r--r--MediaBrowser.ServerApplication/ApplicationHost.cs14
-rw-r--r--MediaBrowser.sln3
6 files changed, 117 insertions, 95 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs
index c76977f29..a8d1e5a0f 100644
--- a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs
@@ -13,6 +13,8 @@ namespace MediaBrowser.Controller.MediaEncoding
public int? MaxHeight { get; set; }
+ public int? Quality { get; set; }
+
public string Format { get; set; }
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/ImageEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/ImageEncoder.cs
index 5e4221b0f..d259c631d 100644
--- a/MediaBrowser.MediaEncoding/Encoder/ImageEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/ImageEncoder.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Logging;
using System;
using System.Diagnostics;
@@ -13,20 +14,39 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
private readonly string _ffmpegPath;
private readonly ILogger _logger;
+ private readonly IFileSystem _fileSystem;
+
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private static readonly SemaphoreSlim ResourcePool = new SemaphoreSlim(5, 5);
+ private static readonly SemaphoreSlim ResourcePool = new SemaphoreSlim(10, 10);
- public ImageEncoder(string ffmpegPath, ILogger logger)
+ public ImageEncoder(string ffmpegPath, ILogger logger, IFileSystem fileSystem)
{
_ffmpegPath = ffmpegPath;
_logger = logger;
+ _fileSystem = fileSystem;
}
public async Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
{
ValidateInput(options);
+ await ResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ return await EncodeImageInternal(options, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ ResourcePool.Release();
+ }
+ }
+
+ private async Task<Stream> EncodeImageInternal(ImageEncodingOptions options, CancellationToken cancellationToken)
+ {
+ ValidateInput(options);
+
var process = new Process
{
StartInfo = new ProcessStartInfo
@@ -38,11 +58,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
RedirectStandardOutput = true,
- RedirectStandardError = true
+ RedirectStandardError = true,
+ WorkingDirectory = Path.GetDirectoryName(options.InputPath)
}
};
- await ResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+ _logger.Debug("ffmpeg " + process.StartInfo.Arguments);
process.Start();
@@ -74,8 +95,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- ResourcePool.Release();
-
var exitCode = ranToCompletion ? process.ExitCode : -1;
process.Dispose();
@@ -94,16 +113,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
memoryStream.Position = 0;
return memoryStream;
}
-
+
private string GetArguments(ImageEncodingOptions options)
{
var vfScale = GetFilterGraph(options);
- var outputFormat = GetOutputFormat(options);
+ var outputFormat = GetOutputFormat(options.Format);
+
+ var quality = (options.Quality ?? 100) * .3;
+ quality = 31 - quality;
+ var qualityValue = Convert.ToInt32(Math.Max(quality, 1));
- return string.Format("-i file:\"{0}\" {1} -f {2}",
- options.InputPath,
+ return string.Format("-f image2 -i file:\"{3}\" -q:v {0} {1} -f image2pipe -vcodec {2} -",
+ qualityValue.ToString(_usCulture),
vfScale,
- outputFormat);
+ outputFormat,
+ Path.GetFileName(options.InputPath));
}
private string GetFilterGraph(ImageEncodingOptions options)
@@ -121,7 +145,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (options.MaxWidth.HasValue)
{
- widthScale = "min(iw," + options.MaxWidth.Value.ToString(_usCulture) + ")";
+ widthScale = "min(iw\\," + options.MaxWidth.Value.ToString(_usCulture) + ")";
}
else if (options.Width.HasValue)
{
@@ -130,7 +154,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (options.MaxHeight.HasValue)
{
- heightScale = "min(ih," + options.MaxHeight.Value.ToString(_usCulture) + ")";
+ heightScale = "min(ih\\," + options.MaxHeight.Value.ToString(_usCulture) + ")";
}
else if (options.Height.HasValue)
{
@@ -139,15 +163,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
var scaleMethod = "lanczos";
- return string.Format("-vf scale=\"{0}:{1}\" -sws_flags {2}",
- widthScale,
+ return string.Format("-vf scale=\"{0}:{1}\" -sws_flags {2}",
+ widthScale,
heightScale,
scaleMethod);
}
- private string GetOutputFormat(ImageEncodingOptions options)
+ private string GetOutputFormat(string format)
{
- return options.Format;
+ if (string.Equals(format, "jpeg", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(format, "jpg", StringComparison.OrdinalIgnoreCase))
+ {
+ return "mjpeg";
+ }
+ return format;
}
private void ValidateInput(ImageEncodingOptions options)
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 55035a4ca..8b41d2105 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -880,45 +880,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
/// <summary>
- /// Starts the and wait for process.
- /// </summary>
- /// <param name="process">The process.</param>
- /// <param name="timeout">The timeout.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- private bool StartAndWaitForProcess(Process process, int timeout = 10000)
- {
- process.Start();
-
- var ranToCompletion = process.WaitForExit(timeout);
-
- if (!ranToCompletion)
- {
- try
- {
- _logger.Info("Killing ffmpeg process");
-
- process.Kill();
-
- process.WaitForExit(1000);
- }
- catch (Win32Exception ex)
- {
- _logger.ErrorException("Error killing process", ex);
- }
- catch (InvalidOperationException ex)
- {
- _logger.ErrorException("Error killing process", ex);
- }
- catch (NotSupportedException ex)
- {
- _logger.ErrorException("Error killing process", ex);
- }
- }
-
- return ranToCompletion;
- }
-
- /// <summary>
/// Gets the file input argument.
/// </summary>
/// <param name="path">The path.</param>
@@ -950,7 +911,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
{
- return new ImageEncoder(FFMpegPath, _logger).EncodeImage(options, cancellationToken);
+ return new ImageEncoder(FFMpegPath, _logger, _fileSystem).EncodeImage(options, cancellationToken);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
index 12d3eadfd..408d8c9b1 100644
--- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
@@ -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;
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index 6083158bc..b7e9017d6 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/MediaBrowser.ServerApplication/ApplicationHost.cs
@@ -473,7 +473,13 @@ namespace MediaBrowser.ServerApplication
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager);
RegisterSingleInstance(LocalizationManager);
- ImageProcessor = new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer);
+ var innerProgress = new ActionableProgress<double>();
+ innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15));
+
+ await RegisterMediaEncoder(innerProgress).ConfigureAwait(false);
+ progress.Report(90);
+
+ ImageProcessor = new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, MediaEncoder);
RegisterSingleInstance(ImageProcessor);
DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager);
@@ -487,12 +493,6 @@ namespace MediaBrowser.ServerApplication
progress.Report(15);
- var innerProgress = new ActionableProgress<double>();
- innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15));
-
- await RegisterMediaEncoder(innerProgress).ConfigureAwait(false);
- progress.Report(90);
-
EncodingManager = new EncodingManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository,
MediaEncoder);
RegisterSingleInstance(EncodingManager);
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index c2f9dff59..7dc06fb0c 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -267,4 +267,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
EndGlobal