From 9a7a5ef50e3284b586480fbcf5dfbc26bdf72634 Mon Sep 17 00:00:00 2001 From: Andrew Rabert Date: Sat, 19 Jan 2019 22:30:16 -0500 Subject: [PATCH 1/3] Replace custom image parser with Skia --- Emby.Drawing/Common/ImageHeader.cs | 242 ----------------------------- Emby.Drawing/Emby.Drawing.csproj | 4 + Emby.Drawing/ImageProcessor.cs | 13 +- 3 files changed, 15 insertions(+), 244 deletions(-) delete mode 100644 Emby.Drawing/Common/ImageHeader.cs diff --git a/Emby.Drawing/Common/ImageHeader.cs b/Emby.Drawing/Common/ImageHeader.cs deleted file mode 100644 index c08cdabb2..000000000 --- a/Emby.Drawing/Common/ImageHeader.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; - -namespace Emby.Drawing.Common -{ - /// - /// Taken from http://stackoverflow.com/questions/111345/getting-image-dimensions-without-reading-the-entire-file/111349 - /// http://www.codeproject.com/Articles/35978/Reading-Image-Headers-to-Get-Width-and-Height - /// Minor improvements including supporting unsigned 16-bit integers when decoding Jfif and added logic - /// to load the image using new Bitmap if reading the headers fails - /// - public static class ImageHeader - { - /// - /// The error message - /// - const string ErrorMessage = "Could not recognize image format."; - - /// - /// The image format decoders - /// - private static readonly KeyValuePair>[] ImageFormatDecoders = new Dictionary> - { - { new byte[] { 0x42, 0x4D }, DecodeBitmap }, - { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif }, - { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif }, - { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng }, - { new byte[] { 0xff, 0xd8 }, DecodeJfif } - - }.ToArray(); - - private static readonly int MaxMagicBytesLength = ImageFormatDecoders.Select(i => i.Key.Length).OrderByDescending(i => i).First(); - - private static string[] SupportedExtensions = new string[] { ".jpg", ".jpeg", ".png", ".gif" }; - - /// - /// Gets the dimensions of an image. - /// - /// The path of the image to get the dimensions of. - /// The logger. - /// The file system. - /// The dimensions of the specified image. - /// The image was of an unrecognised format. - public static ImageSize GetDimensions(string path, ILogger logger, IFileSystem fileSystem) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - string extension = Path.GetExtension(path).ToLower(); - - if (!SupportedExtensions.Contains(extension)) - { - throw new ArgumentException("ImageHeader doesn't support " + extension); - } - - using (var fs = fileSystem.OpenRead(path)) - { - using (var binaryReader = new BinaryReader(fs)) - { - return GetDimensions(binaryReader); - } - } - } - - /// - /// Gets the dimensions of an image. - /// - /// The binary reader. - /// Size. - /// binaryReader - /// The image was of an unrecognized format. - private static ImageSize GetDimensions(BinaryReader binaryReader) - { - var magicBytes = new byte[MaxMagicBytesLength]; - - for (var i = 0; i < MaxMagicBytesLength; i += 1) - { - magicBytes[i] = binaryReader.ReadByte(); - - foreach (var kvPair in ImageFormatDecoders) - { - if (StartsWith(magicBytes, kvPair.Key)) - { - return kvPair.Value(binaryReader); - } - } - } - - throw new ArgumentException(ErrorMessage, nameof(binaryReader)); - } - - /// - /// Startses the with. - /// - /// The this bytes. - /// The that bytes. - /// true if XXXX, false otherwise - private static bool StartsWith(byte[] thisBytes, byte[] thatBytes) - { - for (int i = 0; i < thatBytes.Length; i += 1) - { - if (thisBytes[i] != thatBytes[i]) - { - return false; - } - } - - return true; - } - - /// - /// Reads the little endian int16. - /// - /// The binary reader. - /// System.Int16. - private static short ReadLittleEndianInt16(this BinaryReader binaryReader) - { - var bytes = new byte[sizeof(short)]; - - for (int i = 0; i < sizeof(short); i += 1) - { - bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte(); - } - return BitConverter.ToInt16(bytes, 0); - } - - /// - /// Reads the little endian int32. - /// - /// The binary reader. - /// System.Int32. - private static int ReadLittleEndianInt32(this BinaryReader binaryReader) - { - var bytes = new byte[sizeof(int)]; - for (int i = 0; i < sizeof(int); i += 1) - { - bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte(); - } - return BitConverter.ToInt32(bytes, 0); - } - - /// - /// Decodes the bitmap. - /// - /// The binary reader. - /// Size. - private static ImageSize DecodeBitmap(BinaryReader binaryReader) - { - binaryReader.ReadBytes(16); - int width = binaryReader.ReadInt32(); - int height = binaryReader.ReadInt32(); - return new ImageSize - { - Width = width, - Height = height - }; - } - - /// - /// Decodes the GIF. - /// - /// The binary reader. - /// Size. - private static ImageSize DecodeGif(BinaryReader binaryReader) - { - int width = binaryReader.ReadInt16(); - int height = binaryReader.ReadInt16(); - return new ImageSize - { - Width = width, - Height = height - }; - } - - /// - /// Decodes the PNG. - /// - /// The binary reader. - /// Size. - private static ImageSize DecodePng(BinaryReader binaryReader) - { - binaryReader.ReadBytes(8); - int width = ReadLittleEndianInt32(binaryReader); - int height = ReadLittleEndianInt32(binaryReader); - return new ImageSize - { - Width = width, - Height = height - }; - } - - /// - /// Decodes the jfif. - /// - /// The binary reader. - /// Size. - /// - private static ImageSize DecodeJfif(BinaryReader binaryReader) - { - // A JPEG image consists of a sequence of segments, - // each beginning with a marker, each of which begins with a 0xFF byte - // followed by a byte indicating what kind of marker it is. - // Source: https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure - while (binaryReader.ReadByte() == 0xff) - { - byte marker = binaryReader.ReadByte(); - short chunkLength = binaryReader.ReadLittleEndianInt16(); - // SOF0: Indicates that this is a baseline DCT-based JPEG, - // and specifies the width, height, number of components, and component subsampling - // SOF2: Indicates that this is a progressive DCT-based JPEG, - // and specifies the width, height, number of components, and component subsampling - if (marker == 0xc0 || marker == 0xc2) - { - // https://help.accusoft.com/ImageGear/v18.2/Windows/ActiveX/IGAX-10-12.html - binaryReader.ReadByte(); // We don't care about the first byte - int height = binaryReader.ReadLittleEndianInt16(); - int width = binaryReader.ReadLittleEndianInt16(); - return new ImageSize(width, height); - } - - if (chunkLength < 0) - { - ushort uchunkLength = (ushort)chunkLength; - binaryReader.ReadBytes(uchunkLength - 2); - } - else - { - binaryReader.ReadBytes(chunkLength - 2); - } - } - - throw new ArgumentException(ErrorMessage); - } - } -} diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index ba29c656b..542d7f563 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -5,6 +5,10 @@ + + + + diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 28aae9cae..4095e4176 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -1,3 +1,4 @@ +using SkiaSharp; using System; using System.Collections.Generic; using System.Globalization; @@ -5,7 +6,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Emby.Drawing.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; @@ -472,7 +472,16 @@ namespace Emby.Drawing try { - return ImageHeader.GetDimensions(path, _logger, _fileSystem); + using (var s = new SKFileStream(path)) + using (var codec = SKCodec.Create(s)) + { + var info = codec.Info; + return new ImageSize + { + Height = info.Height, + Width = info.Width + }; + } } catch { From 449dd1a6a242a990a75f3e19afea3b00670d0f5b Mon Sep 17 00:00:00 2001 From: Andrew Rabert Date: Sat, 19 Jan 2019 22:33:55 -0500 Subject: [PATCH 2/3] Remove allowSlowMethods from image processing --- Emby.Drawing/ImageProcessor.cs | 41 ++++++------------- Emby.Photos/PhotoProvider.cs | 2 +- MediaBrowser.Api/Images/ImageService.cs | 2 +- .../Drawing/IImageProcessor.cs | 2 +- 4 files changed, 15 insertions(+), 32 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 4095e4176..f91990442 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -422,10 +422,10 @@ namespace Emby.Drawing public ImageSize GetImageSize(BaseItem item, ItemImageInfo info) { - return GetImageSize(item, info, false, true); + return GetImageSize(item, info, true); } - public ImageSize GetImageSize(BaseItem item, ItemImageInfo info, bool allowSlowMethods, bool updateItem) + public ImageSize GetImageSize(BaseItem item, ItemImageInfo info, bool updateItem) { var width = info.Width; var height = info.Height; @@ -442,7 +442,7 @@ namespace Emby.Drawing var path = info.Path; _logger.LogInformation("Getting image size for item {0} {1}", item.GetType().Name, path); - var size = GetImageSize(path, allowSlowMethods); + var size = GetImageSize(path); info.Height = Convert.ToInt32(size.Height); info.Width = Convert.ToInt32(size.Width); @@ -455,43 +455,26 @@ namespace Emby.Drawing return size; } - public ImageSize GetImageSize(string path) - { - return GetImageSize(path, true); - } - /// /// Gets the size of the image. /// - private ImageSize GetImageSize(string path, bool allowSlowMethod) + public ImageSize GetImageSize(string path) { if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } - try - { - using (var s = new SKFileStream(path)) - using (var codec = SKCodec.Create(s)) - { - var info = codec.Info; - return new ImageSize - { - Height = info.Height, - Width = info.Width - }; - } - } - catch - { - if (!allowSlowMethod) + using (var s = new SKFileStream(path)) + using (var codec = SKCodec.Create(s)) { - throw; + var info = codec.Info; + return new ImageSize + { + Height = info.Height, + Width = info.Width + }; } - } - - return _imageEncoder.GetImageSize(path); } /// diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index 4fcd418f0..4e483ad5b 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -181,7 +181,7 @@ namespace Emby.Photos try { - var size = _imageProcessor.GetImageSize(item, img, false, false); + var size = _imageProcessor.GetImageSize(item, img, false); if (size.Width > 0 && size.Height > 0) { diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 369e9781f..26ac8d40e 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -328,7 +328,7 @@ namespace MediaBrowser.Api.Images var fileInfo = _fileSystem.GetFileInfo(info.Path); length = fileInfo.Length; - var size = _imageProcessor.GetImageSize(item, info, true, true); + var size = _imageProcessor.GetImageSize(item, info, true); width = Convert.ToInt32(size.Width); height = Convert.ToInt32(size.Height); diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index c5d30c426..2cafc9ec1 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Drawing /// ImageSize. ImageSize GetImageSize(BaseItem item, ItemImageInfo info); - ImageSize GetImageSize(BaseItem item, ItemImageInfo info, bool allowSlowMethods, bool updateItem); + ImageSize GetImageSize(BaseItem item, ItemImageInfo info, bool updateItem); /// /// Adds the parts. From 875392c77fce26f2b51142f65998a1d73727310b Mon Sep 17 00:00:00 2001 From: Andrew Rabert Date: Sat, 19 Jan 2019 22:38:41 -0500 Subject: [PATCH 3/3] Combine Emby.Drawing and Emby.Drawing.Skia --- Emby.Drawing.Skia/Emby.Drawing.Skia.csproj | 24 --------------- Emby.Drawing.Skia/Properties/AssemblyInfo.cs | 21 -------------- Emby.Drawing/Emby.Drawing.csproj | 29 ++++++++++--------- .../PercentPlayedDrawer.cs | 2 +- .../PlayedIndicatorDrawer.cs | 2 +- .../SkiaEncoder.cs | 2 +- .../StripCollageBuilder.cs | 2 +- .../UnplayedCountIndicator.cs | 2 +- Jellyfin.Server/Jellyfin.Server.csproj | 1 - Jellyfin.Server/Program.cs | 1 - MediaBrowser.sln | 6 ---- 11 files changed, 21 insertions(+), 71 deletions(-) delete mode 100644 Emby.Drawing.Skia/Emby.Drawing.Skia.csproj delete mode 100644 Emby.Drawing.Skia/Properties/AssemblyInfo.cs rename {Emby.Drawing.Skia => Emby.Drawing}/PercentPlayedDrawer.cs (97%) rename {Emby.Drawing.Skia => Emby.Drawing}/PlayedIndicatorDrawer.cs (98%) rename {Emby.Drawing.Skia => Emby.Drawing}/SkiaEncoder.cs (99%) rename {Emby.Drawing.Skia => Emby.Drawing}/StripCollageBuilder.cs (99%) rename {Emby.Drawing.Skia => Emby.Drawing}/UnplayedCountIndicator.cs (98%) diff --git a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj deleted file mode 100644 index c36d42194..000000000 --- a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - netstandard2.0 - false - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Drawing.Skia/Properties/AssemblyInfo.cs b/Emby.Drawing.Skia/Properties/AssemblyInfo.cs deleted file mode 100644 index 521010cdb..000000000 --- a/Emby.Drawing.Skia/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Emby.Drawing.Skia")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index 542d7f563..c36d42194 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -1,21 +1,24 @@ - - - - - - - - - - - - - netstandard2.0 false + + + + + + + + + + + + + + + + diff --git a/Emby.Drawing.Skia/PercentPlayedDrawer.cs b/Emby.Drawing/PercentPlayedDrawer.cs similarity index 97% rename from Emby.Drawing.Skia/PercentPlayedDrawer.cs rename to Emby.Drawing/PercentPlayedDrawer.cs index a204999fe..3ab5f34bc 100644 --- a/Emby.Drawing.Skia/PercentPlayedDrawer.cs +++ b/Emby.Drawing/PercentPlayedDrawer.cs @@ -2,7 +2,7 @@ using System; using MediaBrowser.Model.Drawing; using SkiaSharp; -namespace Emby.Drawing.Skia +namespace Emby.Drawing { public class PercentPlayedDrawer { diff --git a/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs b/Emby.Drawing/PlayedIndicatorDrawer.cs similarity index 98% rename from Emby.Drawing.Skia/PlayedIndicatorDrawer.cs rename to Emby.Drawing/PlayedIndicatorDrawer.cs index 7e9cb8c5c..92a164a5f 100644 --- a/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs +++ b/Emby.Drawing/PlayedIndicatorDrawer.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; using SkiaSharp; -namespace Emby.Drawing.Skia +namespace Emby.Drawing { public class PlayedIndicatorDrawer { diff --git a/Emby.Drawing.Skia/SkiaEncoder.cs b/Emby.Drawing/SkiaEncoder.cs similarity index 99% rename from Emby.Drawing.Skia/SkiaEncoder.cs rename to Emby.Drawing/SkiaEncoder.cs index 8f517681e..dc571f4a0 100644 --- a/Emby.Drawing.Skia/SkiaEncoder.cs +++ b/Emby.Drawing/SkiaEncoder.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; using SkiaSharp; -namespace Emby.Drawing.Skia +namespace Emby.Drawing { public class SkiaEncoder : IImageEncoder { diff --git a/Emby.Drawing.Skia/StripCollageBuilder.cs b/Emby.Drawing/StripCollageBuilder.cs similarity index 99% rename from Emby.Drawing.Skia/StripCollageBuilder.cs rename to Emby.Drawing/StripCollageBuilder.cs index 8d984de11..dd342998b 100644 --- a/Emby.Drawing.Skia/StripCollageBuilder.cs +++ b/Emby.Drawing/StripCollageBuilder.cs @@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using SkiaSharp; -namespace Emby.Drawing.Skia +namespace Emby.Drawing { public class StripCollageBuilder { diff --git a/Emby.Drawing.Skia/UnplayedCountIndicator.cs b/Emby.Drawing/UnplayedCountIndicator.cs similarity index 98% rename from Emby.Drawing.Skia/UnplayedCountIndicator.cs rename to Emby.Drawing/UnplayedCountIndicator.cs index e1d1ecff8..caa3e465b 100644 --- a/Emby.Drawing.Skia/UnplayedCountIndicator.cs +++ b/Emby.Drawing/UnplayedCountIndicator.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; using SkiaSharp; -namespace Emby.Drawing.Skia +namespace Emby.Drawing { public class UnplayedCountIndicator { diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index f17e06e69..897d93339 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -43,7 +43,6 @@ - diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 51ad53749..d07761619 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -9,7 +9,6 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Emby.Drawing; -using Emby.Drawing.Skia; using Emby.Server.Implementations; using Emby.Server.Implementations.EnvironmentInfo; using Emby.Server.Implementations.IO; diff --git a/MediaBrowser.sln b/MediaBrowser.sln index f1976c3c7..0f0c24a25 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -34,8 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing.Skia", "Emby.Drawing.Skia\Emby.Drawing.Skia.csproj", "{2312DA6D-FF86-4597-9777-BCEEC32D96DD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocketHttpListener", "SocketHttpListener\SocketHttpListener.csproj", "{1D74413B-E7CF-455B-B021-F52BDF881542}" @@ -130,10 +128,6 @@ Global {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU - {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Any CPU.Build.0 = Release|Any CPU {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU