From dec3b1bbb0f53e1dafcf0bd9238aaefbfd18f335 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Dec 2017 12:04:32 -0500 Subject: [PATCH] improve image processing performance --- Emby.Dlna/PlayTo/PlayToController.cs | 2 +- .../ImageMagickEncoder.cs | 2 + Emby.Drawing.Skia/SkiaEncoder.cs | 2 + Emby.Drawing/ImageProcessor.cs | 104 ++++++++++++------ .../Social/SharingManager.cs | 4 +- 5 files changed, 80 insertions(+), 34 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 9b2c703940..b253cb26e7 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -430,7 +430,7 @@ namespace Emby.Dlna.PlayTo return Task.FromResult(true); } - public Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken) + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) { return Task.FromResult(true); } diff --git a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs index 38e2879ea8..3b3f7cf0ab 100644 --- a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs +++ b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs @@ -173,6 +173,8 @@ namespace Emby.Drawing.ImageMagick originalImage.CurrentImage.CompressionQuality = quality; originalImage.CurrentImage.StripImage(); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); + originalImage.SaveImage(outputPath); } } diff --git a/Emby.Drawing.Skia/SkiaEncoder.cs b/Emby.Drawing.Skia/SkiaEncoder.cs index a89e1f2db8..9b4f1fc58d 100644 --- a/Emby.Drawing.Skia/SkiaEncoder.cs +++ b/Emby.Drawing.Skia/SkiaEncoder.cs @@ -528,6 +528,7 @@ namespace Emby.Drawing.Skia // If all we're doing is resizing then we can stop now if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) { + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); using (var outputStream = new SKFileWStream(outputPath)) { resizedBitmap.Encode(outputStream, skiaOutputFormat, quality); @@ -580,6 +581,7 @@ namespace Emby.Drawing.Skia DrawIndicator(canvas, width, height, options); } + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); using (var outputStream = new SKFileWStream(outputPath)) { saveBitmap.Encode(outputStream, skiaOutputFormat, quality); diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index e28d22cf7b..f91cb4675c 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -220,7 +220,7 @@ namespace Emby.Drawing Type = originalImage.Type, Path = originalImagePath - }, requiresTransparency, item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); + }, requiresTransparency, item, options.ImageIndex, options.Enhancers, CancellationToken.None).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; @@ -256,31 +256,29 @@ namespace Emby.Drawing var outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); + CheckDisposed(); + + var lockInfo = GetLock(cacheFilePath); + + await lockInfo.Lock.WaitAsync().ConfigureAwait(false); + try { - CheckDisposed(); - if (!_fileSystem.FileExists(cacheFilePath)) { - var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath)); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath)); - if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath)) { options.CropWhiteSpace = false; } - var resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, tmpPath, autoOrient, orientation, quality, options, outputFormat); + var resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); - CopyFile(tmpPath, cacheFilePath); - - return new Tuple(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath)); + return new Tuple(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); } return new Tuple(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); @@ -302,6 +300,10 @@ namespace Emby.Drawing // Just spit out the original file if all the options are default return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } + finally + { + ReleaseLock(cacheFilePath, lockInfo); + } } private ImageFormat GetOutputFormat(ImageFormat[] clientSupportedFormats, bool requiresTransparency) @@ -667,7 +669,7 @@ namespace Emby.Drawing var inputImageSupportsTransparency = SupportsTransparency(imageInfo.Path); - var result = await GetEnhancedImage(imageInfo, inputImageSupportsTransparency, item, imageIndex, enhancers); + var result = await GetEnhancedImage(imageInfo, inputImageSupportsTransparency, item, imageIndex, enhancers, CancellationToken.None); return result.Item1; } @@ -676,7 +678,8 @@ namespace Emby.Drawing bool inputImageSupportsTransparency, IHasMetadata item, int imageIndex, - List enhancers) + List enhancers, + CancellationToken cancellationToken) { var originalImagePath = image.Path; var dateModified = image.DateModified; @@ -687,7 +690,7 @@ namespace Emby.Drawing var cacheGuid = GetImageCacheTag(item, image, enhancers); // Enhance if we have enhancers - var ehnancedImageInfo = await GetEnhancedImageInternal(originalImagePath, item, imageType, imageIndex, enhancers, cacheGuid).ConfigureAwait(false); + var ehnancedImageInfo = await GetEnhancedImageInternal(originalImagePath, item, imageType, imageIndex, enhancers, cacheGuid, cancellationToken).ConfigureAwait(false); var ehnancedImagePath = ehnancedImageInfo.Item1; @@ -727,7 +730,8 @@ namespace Emby.Drawing ImageType imageType, int imageIndex, List supportedEnhancers, - string cacheGuid) + string cacheGuid, + CancellationToken cancellationToken) { if (string.IsNullOrEmpty(originalImagePath)) { @@ -755,29 +759,28 @@ namespace Emby.Drawing var enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + cacheExtension); - // Check again in case of contention - if (_fileSystem.FileExists(enhancedImagePath)) - { - return new Tuple(enhancedImagePath, treatmentRequiresTransparency); - } + var lockInfo = GetLock(enhancedImagePath); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(enhancedImagePath)); - - var tmpPath = Path.Combine(_appPaths.TempDirectory, Path.ChangeExtension(Guid.NewGuid().ToString(), Path.GetExtension(enhancedImagePath))); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath)); - - await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, tmpPath, item, imageType, imageIndex).ConfigureAwait(false); + await lockInfo.Lock.WaitAsync(cancellationToken).ConfigureAwait(false); try { - _fileSystem.CopyFile(tmpPath, enhancedImagePath, true); + // Check again in case of contention + if (_fileSystem.FileExists(enhancedImagePath)) + { + return new Tuple(enhancedImagePath, treatmentRequiresTransparency); + } + + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(enhancedImagePath)); + + await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false); + + return new Tuple(enhancedImagePath, treatmentRequiresTransparency); } - catch + finally { - + ReleaseLock(enhancedImagePath, lockInfo); } - - return new Tuple(tmpPath, treatmentRequiresTransparency); } /// @@ -896,6 +899,45 @@ namespace Emby.Drawing return list; } + private Dictionary _locks = new Dictionary(); + private class LockInfo + { + public SemaphoreSlim Lock = new SemaphoreSlim(1, 1); + public int Count = 1; + } + private LockInfo GetLock(string key) + { + lock (_locks) + { + LockInfo info; + if (_locks.TryGetValue(key, out info)) + { + info.Count++; + } + else + { + info = new LockInfo(); + _locks[key] = info; + } + return info; + } + } + + private void ReleaseLock(string key, LockInfo info) + { + info.Lock.Release(); + + lock (_locks) + { + info.Count--; + if (info.Count <= 0) + { + _locks.Remove(key); + info.Lock.Dispose(); + } + } + } + private bool _disposed; public void Dispose() { diff --git a/Emby.Server.Implementations/Social/SharingManager.cs b/Emby.Server.Implementations/Social/SharingManager.cs index e94b9100a3..23ce7492ad 100644 --- a/Emby.Server.Implementations/Social/SharingManager.cs +++ b/Emby.Server.Implementations/Social/SharingManager.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.Social throw new ResourceNotFoundException(); } - var externalUrl = (await _appHost.GetSystemInfo(CancellationToken.None).ConfigureAwait(false)).WanAddress; + var externalUrl = (await _appHost.GetPublicSystemInfo(CancellationToken.None).ConfigureAwait(false)).WanAddress; if (string.IsNullOrWhiteSpace(externalUrl)) { @@ -74,7 +74,7 @@ namespace Emby.Server.Implementations.Social { var info = _repository.GetShareInfo(id); - AddShareInfo(info, _appHost.GetSystemInfo(CancellationToken.None).Result.WanAddress); + AddShareInfo(info, _appHost.GetPublicSystemInfo(CancellationToken.None).Result.WanAddress); return info; }