mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-10-31 10:37:22 -04:00 
			
		
		
		
	
						commit
						6ee9da3717
					
				| @ -53,6 +53,11 @@ namespace Emby.Common.Implementations.IO | ||||
|             if (separator == '/') | ||||
|             { | ||||
|                 result = result.Replace('\\', '/'); | ||||
| 
 | ||||
|                 if (result.StartsWith("smb:/", StringComparison.OrdinalIgnoreCase) && !result.StartsWith("smb://", StringComparison.OrdinalIgnoreCase)) | ||||
|                 { | ||||
|                     result = result.Replace("smb:/", "smb://"); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return result; | ||||
|  | ||||
| @ -105,17 +105,6 @@ namespace Emby.Drawing.ImageMagick | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void CropWhiteSpace(string inputPath, string outputPath) | ||||
|         { | ||||
|             CheckDisposed(); | ||||
| 
 | ||||
|             using (var wand = new MagickWand(inputPath)) | ||||
|             { | ||||
|                 wand.CurrentImage.TrimImage(10); | ||||
|                 wand.SaveImage(outputPath); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public ImageSize GetImageSize(string path) | ||||
|         { | ||||
|             CheckDisposed(); | ||||
| @ -150,6 +139,11 @@ namespace Emby.Drawing.ImageMagick | ||||
|             { | ||||
|                 using (var originalImage = new MagickWand(inputPath)) | ||||
|                 { | ||||
|                     if (options.CropWhiteSpace) | ||||
|                     { | ||||
|                         originalImage.CurrentImage.TrimImage(10); | ||||
|                     } | ||||
| 
 | ||||
|                     ScaleImage(originalImage, width, height, options.Blur ?? 0); | ||||
| 
 | ||||
|                     if (autoOrient) | ||||
|  | ||||
| @ -75,27 +75,24 @@ namespace Emby.Drawing.Net | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void CropWhiteSpace(string inputPath, string outputPath) | ||||
|         private Image GetImage(string path, bool cropWhitespace) | ||||
|         { | ||||
|             using (var image = (Bitmap)Image.FromFile(inputPath)) | ||||
|             if (cropWhitespace) | ||||
|             { | ||||
|                 using (var croppedImage = image.CropWhitespace()) | ||||
|                 using (var originalImage = (Bitmap)Image.FromFile(path)) | ||||
|                 { | ||||
|                     _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); | ||||
| 
 | ||||
|                     using (var outputStream = _fileSystem.GetFileStream(outputPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, false)) | ||||
|                     { | ||||
|                         croppedImage.Save(System.Drawing.Imaging.ImageFormat.Png, outputStream, 100); | ||||
|                     } | ||||
|                     return originalImage.CropWhitespace(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return Image.FromFile(path); | ||||
|         } | ||||
| 
 | ||||
|         public void EncodeImage(string inputPath, string cacheFilePath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) | ||||
|         { | ||||
|             var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed > 0; | ||||
| 
 | ||||
|             using (var originalImage = Image.FromFile(inputPath)) | ||||
|             using (var originalImage = GetImage(inputPath, options.CropWhiteSpace)) | ||||
|             { | ||||
|                 var newWidth = Convert.ToInt32(width); | ||||
|                 var newHeight = Convert.ToInt32(height); | ||||
|  | ||||
| @ -52,7 +52,12 @@ | ||||
|     <Compile Include="..\SharedVersion.cs"> | ||||
|       <Link>Properties\SharedVersion.cs</Link> | ||||
|     </Compile> | ||||
|     <Compile Include="PercentPlayedDrawer.cs" /> | ||||
|     <Compile Include="PlayedIndicatorDrawer.cs" /> | ||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
|     <Compile Include="SkiaEncoder.cs" /> | ||||
|     <Compile Include="StripCollageBuilder.cs" /> | ||||
|     <Compile Include="UnplayedCountIndicator.cs" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Reference Include="SkiaSharp, Version=1.57.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL"> | ||||
| @ -61,6 +66,7 @@ | ||||
|     </Reference> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <EmbeddedResource Include="fonts\robotoregular.ttf" /> | ||||
|     <None Include="packages.config" /> | ||||
|   </ItemGroup> | ||||
|   <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> | ||||
|  | ||||
							
								
								
									
										31
									
								
								Emby.Drawing.Skia/PercentPlayedDrawer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								Emby.Drawing.Skia/PercentPlayedDrawer.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| using SkiaSharp; | ||||
| using MediaBrowser.Model.Drawing; | ||||
| using System; | ||||
| 
 | ||||
| namespace Emby.Drawing.Skia | ||||
| { | ||||
|     public class PercentPlayedDrawer | ||||
|     { | ||||
|         private const int IndicatorHeight = 8; | ||||
| 
 | ||||
|         public void Process(SKCanvas canvas, ImageSize imageSize, double percent) | ||||
|         { | ||||
|             using (var paint = new SKPaint()) | ||||
|             { | ||||
|                 var endX = imageSize.Width - 1; | ||||
|                 var endY = imageSize.Height - 1; | ||||
| 
 | ||||
|                 paint.Color = SKColor.Parse("#99000000"); | ||||
|                 paint.Style = SKPaintStyle.Fill; | ||||
|                 canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, (float)endX, (float)endY), paint); | ||||
| 
 | ||||
|                 double foregroundWidth = endX; | ||||
|                 foregroundWidth *= percent; | ||||
|                 foregroundWidth /= 100; | ||||
| 
 | ||||
|                 paint.Color = SKColor.Parse("#FF52B54B"); | ||||
|                 canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(Math.Round(foregroundWidth)), (float)endY), paint); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										120
									
								
								Emby.Drawing.Skia/PlayedIndicatorDrawer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								Emby.Drawing.Skia/PlayedIndicatorDrawer.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | ||||
| using SkiaSharp; | ||||
| using MediaBrowser.Common.Configuration; | ||||
| using MediaBrowser.Common.Net; | ||||
| using MediaBrowser.Model.Drawing; | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Threading.Tasks; | ||||
| using MediaBrowser.Common.IO; | ||||
| using MediaBrowser.Controller.IO; | ||||
| using MediaBrowser.Model.IO; | ||||
| using System.Reflection; | ||||
| 
 | ||||
| namespace Emby.Drawing.Skia | ||||
| { | ||||
|     public class PlayedIndicatorDrawer | ||||
|     { | ||||
|         private const int FontSize = 42; | ||||
|         private const int OffsetFromTopRightCorner = 38; | ||||
| 
 | ||||
|         private readonly IApplicationPaths _appPaths; | ||||
|         private readonly IHttpClient _iHttpClient; | ||||
|         private readonly IFileSystem _fileSystem; | ||||
| 
 | ||||
|         public PlayedIndicatorDrawer(IApplicationPaths appPaths, IHttpClient iHttpClient, IFileSystem fileSystem) | ||||
|         { | ||||
|             _appPaths = appPaths; | ||||
|             _iHttpClient = iHttpClient; | ||||
|             _fileSystem = fileSystem; | ||||
|         } | ||||
| 
 | ||||
|         public async Task DrawPlayedIndicator(SKCanvas canvas, ImageSize imageSize) | ||||
|         { | ||||
|             var x = imageSize.Width - OffsetFromTopRightCorner; | ||||
| 
 | ||||
|             using (var paint = new SKPaint()) | ||||
|             { | ||||
|                 paint.Color = SKColor.Parse("#CC52B54B"); | ||||
|                 paint.Style = SKPaintStyle.Fill; | ||||
|                 canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint); | ||||
|             } | ||||
| 
 | ||||
|             using (var paint = new SKPaint()) | ||||
|             { | ||||
|                 paint.Color = new SKColor(255, 255, 255, 255); | ||||
|                 paint.Style = SKPaintStyle.Fill; | ||||
|                 paint.Typeface = SKTypeface.FromFile(await DownloadFont("webdings.ttf", "https://github.com/MediaBrowser/Emby.Resources/raw/master/fonts/webdings.ttf", | ||||
|                     _appPaths, _iHttpClient, _fileSystem).ConfigureAwait(false)); | ||||
|                 paint.TextSize = FontSize; | ||||
|                 paint.IsAntialias = true; | ||||
| 
 | ||||
|                 canvas.DrawText("a", (float)x-20, OffsetFromTopRightCorner + 12, paint); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         internal static string ExtractFont(string name, IApplicationPaths paths, IFileSystem fileSystem) | ||||
|         { | ||||
|             var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name); | ||||
| 
 | ||||
|             if (fileSystem.FileExists(filePath)) | ||||
|             { | ||||
|                 return filePath; | ||||
|             } | ||||
| 
 | ||||
|             var namespacePath = typeof(PlayedIndicatorDrawer).Namespace + ".fonts." + name; | ||||
|             var tempPath = Path.Combine(paths.TempDirectory, Guid.NewGuid().ToString("N") + ".ttf"); | ||||
|             fileSystem.CreateDirectory(fileSystem.GetDirectoryName(tempPath)); | ||||
| 
 | ||||
|             using (var stream = typeof(PlayedIndicatorDrawer).GetTypeInfo().Assembly.GetManifestResourceStream(namespacePath)) | ||||
|             { | ||||
|                 using (var fileStream = fileSystem.GetFileStream(tempPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) | ||||
|                 { | ||||
|                     stream.CopyTo(fileStream); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             fileSystem.CreateDirectory(fileSystem.GetDirectoryName(filePath)); | ||||
| 
 | ||||
|             try | ||||
|             { | ||||
|                 fileSystem.CopyFile(tempPath, filePath, false); | ||||
|             } | ||||
|             catch (IOException) | ||||
|             { | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             return tempPath; | ||||
|         } | ||||
| 
 | ||||
|         internal static async Task<string> DownloadFont(string name, string url, IApplicationPaths paths, IHttpClient httpClient, IFileSystem fileSystem) | ||||
|         { | ||||
|             var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name); | ||||
| 
 | ||||
|             if (fileSystem.FileExists(filePath)) | ||||
|             { | ||||
|                 return filePath; | ||||
|             } | ||||
| 
 | ||||
|             var tempPath = await httpClient.GetTempFile(new HttpRequestOptions | ||||
|             { | ||||
|                 Url = url, | ||||
|                 Progress = new Progress<double>() | ||||
| 
 | ||||
|             }).ConfigureAwait(false); | ||||
| 
 | ||||
|             fileSystem.CreateDirectory(fileSystem.GetDirectoryName(filePath)); | ||||
| 
 | ||||
|             try | ||||
|             { | ||||
|                 fileSystem.CopyFile(tempPath, filePath, false); | ||||
|             } | ||||
|             catch (IOException) | ||||
|             { | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             return tempPath; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										380
									
								
								Emby.Drawing.Skia/SkiaEncoder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								Emby.Drawing.Skia/SkiaEncoder.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,380 @@ | ||||
| using MediaBrowser.Common.Configuration; | ||||
| using MediaBrowser.Common.Net; | ||||
| using MediaBrowser.Controller.Drawing; | ||||
| using MediaBrowser.Model.Drawing; | ||||
| using MediaBrowser.Model.IO; | ||||
| using MediaBrowser.Model.Logging; | ||||
| using SkiaSharp; | ||||
| using System; | ||||
| using System.Reflection; | ||||
| using System.Threading.Tasks; | ||||
| 
 | ||||
| namespace Emby.Drawing.Skia | ||||
| { | ||||
|     public class SkiaEncoder : IImageEncoder | ||||
|     { | ||||
|         private readonly ILogger _logger; | ||||
|         private readonly IApplicationPaths _appPaths; | ||||
|         private readonly Func<IHttpClient> _httpClientFactory; | ||||
|         private readonly IFileSystem _fileSystem; | ||||
| 
 | ||||
|         public SkiaEncoder(ILogger logger, IApplicationPaths appPaths, Func<IHttpClient> httpClientFactory, IFileSystem fileSystem) | ||||
|         { | ||||
|             _logger = logger; | ||||
|             _appPaths = appPaths; | ||||
|             _httpClientFactory = httpClientFactory; | ||||
|             _fileSystem = fileSystem; | ||||
| 
 | ||||
|             LogVersion(); | ||||
|         } | ||||
| 
 | ||||
|         public string[] SupportedInputFormats | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 // Some common file name extensions for RAW picture files include: .cr2, .crw, .dng, .nef, .orf, .rw2, .pef, .arw, .sr2, .srf, and .tif. | ||||
|                 return new[] | ||||
|                 { | ||||
|                     "jpeg", | ||||
|                     "jpg", | ||||
|                     "png", | ||||
|                     "dng", | ||||
|                     "webp", | ||||
|                     "gif", | ||||
|                     "bmp", | ||||
|                     "ico", | ||||
|                     "astc", | ||||
|                     "ktx", | ||||
|                     "pkm", | ||||
|                     "wbmp" | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public ImageFormat[] SupportedOutputFormats | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 return new[] { ImageFormat.Webp, ImageFormat.Gif, ImageFormat.Jpg, ImageFormat.Png, ImageFormat.Bmp }; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void LogVersion() | ||||
|         { | ||||
|             _logger.Info("SkiaSharp version: " + GetVersion()); | ||||
|         } | ||||
| 
 | ||||
|         public static string GetVersion() | ||||
|         { | ||||
|             using (var bitmap = new SKBitmap()) | ||||
|             { | ||||
|                 return typeof(SKBitmap).GetTypeInfo().Assembly.GetName().Version.ToString(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private static bool IsWhiteSpace(SKColor color) | ||||
|         { | ||||
|             return (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0; | ||||
|         } | ||||
| 
 | ||||
|         public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat) | ||||
|         { | ||||
|             switch (selectedFormat) | ||||
|             { | ||||
|                 case ImageFormat.Bmp: | ||||
|                     return SKEncodedImageFormat.Bmp; | ||||
|                 case ImageFormat.Jpg: | ||||
|                     return SKEncodedImageFormat.Jpeg; | ||||
|                 case ImageFormat.Gif: | ||||
|                     return SKEncodedImageFormat.Gif; | ||||
|                 case ImageFormat.Webp: | ||||
|                     return SKEncodedImageFormat.Webp; | ||||
|                 default: | ||||
|                     return SKEncodedImageFormat.Png; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private static bool IsAllWhiteRow(SKBitmap bmp, int row) | ||||
|         { | ||||
|             for (var i = 0; i < bmp.Width; ++i) | ||||
|             { | ||||
|                 if (!IsWhiteSpace(bmp.GetPixel(i, row))) | ||||
|                 { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         private static bool IsAllWhiteColumn(SKBitmap bmp, int col) | ||||
|         { | ||||
|             for (var i = 0; i < bmp.Height; ++i) | ||||
|             { | ||||
|                 if (!IsWhiteSpace(bmp.GetPixel(col, i))) | ||||
|                 { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         private SKBitmap CropWhiteSpace(SKBitmap bitmap) | ||||
|         { | ||||
|             CheckDisposed(); | ||||
| 
 | ||||
|             var topmost = 0; | ||||
|             for (int row = 0; row < bitmap.Height; ++row) | ||||
|             { | ||||
|                 if (IsAllWhiteRow(bitmap, row)) | ||||
|                     topmost = row; | ||||
|                 else break; | ||||
|             } | ||||
| 
 | ||||
|             int bottommost = 0; | ||||
|             for (int row = bitmap.Height - 1; row >= 0; --row) | ||||
|             { | ||||
|                 if (IsAllWhiteRow(bitmap, row)) | ||||
|                     bottommost = row; | ||||
|                 else break; | ||||
|             } | ||||
| 
 | ||||
|             int leftmost = 0, rightmost = 0; | ||||
|             for (int col = 0; col < bitmap.Width; ++col) | ||||
|             { | ||||
|                 if (IsAllWhiteColumn(bitmap, col)) | ||||
|                     leftmost = col; | ||||
|                 else | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             for (int col = bitmap.Width - 1; col >= 0; --col) | ||||
|             { | ||||
|                 if (IsAllWhiteColumn(bitmap, col)) | ||||
|                     rightmost = col; | ||||
|                 else | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost); | ||||
| 
 | ||||
|             using (var image = SKImage.FromBitmap(bitmap)) | ||||
|             { | ||||
|                 using (var subset = image.Subset(newRect)) | ||||
|                 { | ||||
|                     return SKBitmap.FromImage(subset); | ||||
|                     //using (var data = subset.Encode(StripCollageBuilder.GetEncodedFormat(outputPath), 90)) | ||||
|                     //{ | ||||
|                     //    using (var fileStream = _fileSystem.GetFileStream(outputPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) | ||||
|                     //    { | ||||
|                     //        data.AsStream().CopyTo(fileStream); | ||||
|                     //    } | ||||
|                     //} | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public ImageSize GetImageSize(string path) | ||||
|         { | ||||
|             CheckDisposed(); | ||||
| 
 | ||||
|             using (var s = new SKFileStream(path)) | ||||
|             { | ||||
|                 using (var codec = SKCodec.Create(s)) | ||||
|                 { | ||||
|                     var info = codec.Info; | ||||
| 
 | ||||
|                     return new ImageSize | ||||
|                     { | ||||
|                         Width = info.Width, | ||||
|                         Height = info.Height | ||||
|                     }; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private SKBitmap GetBitmap(string path, bool cropWhitespace) | ||||
|         { | ||||
|             if (cropWhitespace) | ||||
|             { | ||||
|                 using (var bitmap = SKBitmap.Decode(path)) | ||||
|                 { | ||||
|                     return CropWhiteSpace(bitmap); | ||||
|                 } | ||||
|             }  | ||||
| 
 | ||||
|             return SKBitmap.Decode(path); | ||||
|         } | ||||
| 
 | ||||
|         public void EncodeImage(string inputPath, string outputPath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) | ||||
|         { | ||||
|             if (string.IsNullOrWhiteSpace(inputPath)) | ||||
|             { | ||||
|                 throw new ArgumentNullException("inputPath"); | ||||
|             } | ||||
|             if (string.IsNullOrWhiteSpace(inputPath)) | ||||
|             { | ||||
|                 throw new ArgumentNullException("outputPath"); | ||||
|             } | ||||
| 
 | ||||
|             var skiaOutputFormat = GetImageFormat(selectedOutputFormat); | ||||
| 
 | ||||
|             var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor); | ||||
|             var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer); | ||||
|             var blur = options.Blur ?? 0; | ||||
|             var hasIndicator = !options.AddPlayedIndicator && !options.UnplayedCount.HasValue && options.PercentPlayed.Equals(0); | ||||
| 
 | ||||
|             using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace)) | ||||
|             { | ||||
|                 using (var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType)) | ||||
|                 { | ||||
|                     // scale image | ||||
|                     var resizeMethod = options.Image.Type == MediaBrowser.Model.Entities.ImageType.Logo || | ||||
|                                        options.Image.Type == MediaBrowser.Model.Entities.ImageType.Art | ||||
|                         ? SKBitmapResizeMethod.Lanczos3 | ||||
|                         : SKBitmapResizeMethod.Lanczos3; | ||||
| 
 | ||||
|                     bitmap.Resize(resizedBitmap, resizeMethod); | ||||
| 
 | ||||
|                     // If all we're doing is resizing then we can stop now | ||||
|                     if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) | ||||
|                     { | ||||
|                         using (var outputStream = new SKFileWStream(outputPath)) | ||||
|                         { | ||||
|                             resizedBitmap.Encode(outputStream, skiaOutputFormat, quality); | ||||
|                             return; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     // create bitmap to use for canvas drawing | ||||
|                     using (var saveBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType)) | ||||
|                     { | ||||
|                         // create canvas used to draw into bitmap | ||||
|                         using (var canvas = new SKCanvas(saveBitmap)) | ||||
|                         { | ||||
|                             // set background color if present | ||||
|                             if (hasBackgroundColor) | ||||
|                             { | ||||
|                                 canvas.Clear(SKColor.Parse(options.BackgroundColor)); | ||||
|                             } | ||||
| 
 | ||||
|                             // Add blur if option is present | ||||
|                             if (blur > 0) | ||||
|                             { | ||||
|                                 using (var paint = new SKPaint()) | ||||
|                                 { | ||||
|                                     // create image from resized bitmap to apply blur | ||||
|                                     using (var filter = SKImageFilter.CreateBlur(5, 5)) | ||||
|                                     { | ||||
|                                         paint.ImageFilter = filter; | ||||
|                                         canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint); | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 // draw resized bitmap onto canvas | ||||
|                                 canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height)); | ||||
|                             } | ||||
| 
 | ||||
|                             // If foreground layer present then draw | ||||
|                             if (hasForegroundColor) | ||||
|                             { | ||||
|                                 Double opacity; | ||||
|                                 if (!Double.TryParse(options.ForegroundLayer, out opacity)) opacity = .4; | ||||
| 
 | ||||
|                                 var foregroundColor = String.Format("#{0:X2}000000", (Byte)((1 - opacity) * 0xFF)); | ||||
|                                 canvas.DrawColor(SKColor.Parse(foregroundColor), SKBlendMode.SrcOver); | ||||
|                             } | ||||
| 
 | ||||
|                             if (hasIndicator) | ||||
|                             { | ||||
|                                 DrawIndicator(canvas, width, height, options); | ||||
|                             } | ||||
| 
 | ||||
|                             using (var outputStream = new SKFileWStream(outputPath)) | ||||
|                             { | ||||
|                                 saveBitmap.Encode(outputStream, skiaOutputFormat, quality); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void CreateImageCollage(ImageCollageOptions options) | ||||
|         { | ||||
|             double ratio = options.Width; | ||||
|             ratio /= options.Height; | ||||
| 
 | ||||
|             if (ratio >= 1.4) | ||||
|             { | ||||
|                 new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); | ||||
|             } | ||||
|             else if (ratio >= .9) | ||||
|             { | ||||
|                 new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // @todo create Poster collage capability | ||||
|                 new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void DrawIndicator(SKCanvas canvas, int imageWidth, int imageHeight, ImageProcessingOptions options) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var currentImageSize = new ImageSize(imageWidth, imageHeight); | ||||
| 
 | ||||
|                 if (options.AddPlayedIndicator) | ||||
|                 { | ||||
|                     var task = new PlayedIndicatorDrawer(_appPaths, _httpClientFactory(), _fileSystem).DrawPlayedIndicator(canvas, currentImageSize); | ||||
|                     Task.WaitAll(task); | ||||
|                 } | ||||
|                 else if (options.UnplayedCount.HasValue) | ||||
|                 { | ||||
|                     new UnplayedCountIndicator(_appPaths, _httpClientFactory(), _fileSystem).DrawUnplayedCountIndicator(canvas, currentImageSize, options.UnplayedCount.Value); | ||||
|                 } | ||||
| 
 | ||||
|                 if (options.PercentPlayed > 0) | ||||
|                 { | ||||
|                     new PercentPlayedDrawer().Process(canvas, currentImageSize, options.PercentPlayed); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger.ErrorException("Error drawing indicator overlay", ex); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public string Name | ||||
|         { | ||||
|             get { return "Skia"; } | ||||
|         } | ||||
| 
 | ||||
|         private bool _disposed; | ||||
|         public void Dispose() | ||||
|         { | ||||
|             _disposed = true; | ||||
|         } | ||||
| 
 | ||||
|         private void CheckDisposed() | ||||
|         { | ||||
|             if (_disposed) | ||||
|             { | ||||
|                 throw new ObjectDisposedException(GetType().Name); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool SupportsImageCollageCreation | ||||
|         { | ||||
|             get { return true; } | ||||
|         } | ||||
| 
 | ||||
|         public bool SupportsImageEncoding | ||||
|         { | ||||
|             get { return true; } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										164
									
								
								Emby.Drawing.Skia/StripCollageBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								Emby.Drawing.Skia/StripCollageBuilder.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,164 @@ | ||||
| using SkiaSharp; | ||||
| using MediaBrowser.Common.Configuration; | ||||
| using System; | ||||
| using System.IO; | ||||
| using MediaBrowser.Model.IO; | ||||
| 
 | ||||
| namespace Emby.Drawing.Skia | ||||
| { | ||||
|     public class StripCollageBuilder | ||||
|     { | ||||
|         private readonly IApplicationPaths _appPaths; | ||||
|         private readonly IFileSystem _fileSystem; | ||||
| 
 | ||||
|         public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem) | ||||
|         { | ||||
|             _appPaths = appPaths; | ||||
|             _fileSystem = fileSystem; | ||||
|         } | ||||
| 
 | ||||
|         public static SKEncodedImageFormat GetEncodedFormat(string outputPath) | ||||
|         { | ||||
|             var ext = Path.GetExtension(outputPath).ToLower(); | ||||
| 
 | ||||
|             if (ext == ".jpg" || ext == ".jpeg") | ||||
|                 return SKEncodedImageFormat.Jpeg; | ||||
| 
 | ||||
|             if (ext == ".webp") | ||||
|                 return SKEncodedImageFormat.Webp; | ||||
| 
 | ||||
|             if (ext == ".gif") | ||||
|                 return SKEncodedImageFormat.Gif; | ||||
| 
 | ||||
|             if (ext == ".bmp") | ||||
|                 return SKEncodedImageFormat.Bmp; | ||||
| 
 | ||||
|             // default to png | ||||
|             return SKEncodedImageFormat.Png; | ||||
|         } | ||||
| 
 | ||||
|         public void BuildPosterCollage(string[] paths, string outputPath, int width, int height) | ||||
|         { | ||||
|             // @todo | ||||
|         } | ||||
| 
 | ||||
|         public void BuildSquareCollage(string[] paths, string outputPath, int width, int height) | ||||
|         { | ||||
|             using (var bitmap = BuildSquareCollageBitmap(paths, width, height)) | ||||
|             { | ||||
|                 using (var outputStream = new SKFileWStream(outputPath)) | ||||
|                 { | ||||
|                     bitmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void BuildThumbCollage(string[] paths, string outputPath, int width, int height) | ||||
|         { | ||||
|             using (var bitmap = BuildThumbCollageBitmap(paths, width, height)) | ||||
|             { | ||||
|                 using (var outputStream = new SKFileWStream(outputPath)) | ||||
|                 { | ||||
|                     bitmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height) | ||||
|         { | ||||
|             var bitmap = new SKBitmap(width, height); | ||||
| 
 | ||||
|             using (var canvas = new SKCanvas(bitmap)) | ||||
|             { | ||||
|                 canvas.Clear(SKColors.Black); | ||||
| 
 | ||||
|                 var iSlice = Convert.ToInt32(width * 0.24125); | ||||
|                 int iTrans = Convert.ToInt32(height * .25); | ||||
|                 int iHeight = Convert.ToInt32(height * .70); | ||||
|                 var horizontalImagePadding = Convert.ToInt32(width * 0.0125); | ||||
|                 var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); | ||||
|                 int imageIndex = 0; | ||||
| 
 | ||||
|                 for (int i = 0; i < 4; i++) | ||||
|                 { | ||||
|                     using (var currentBitmap = SKBitmap.Decode(paths[imageIndex])) | ||||
|                     { | ||||
|                         int iWidth = (int)Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); | ||||
|                         using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) | ||||
|                         { | ||||
|                             currentBitmap.Resize(resizeBitmap, SKBitmapResizeMethod.Lanczos3); | ||||
|                             int ix = (int)Math.Abs((iWidth - iSlice) / 2); | ||||
|                             using (var image = SKImage.FromBitmap(resizeBitmap)) | ||||
|                             { | ||||
|                                 using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight))) | ||||
|                                 { | ||||
|                                     canvas.DrawImage(subset, (horizontalImagePadding * (i + 1)) + (iSlice * i), 0); | ||||
| 
 | ||||
|                                     using (var croppedBitmap = SKBitmap.FromImage(subset)) | ||||
|                                     { | ||||
|                                         using (var flipped = new SKBitmap(croppedBitmap.Width, croppedBitmap.Height / 2, croppedBitmap.ColorType, croppedBitmap.AlphaType)) | ||||
|                                         { | ||||
|                                             croppedBitmap.Resize(flipped, SKBitmapResizeMethod.Lanczos3); | ||||
| 
 | ||||
|                                             using (var gradient = new SKPaint()) | ||||
|                                             { | ||||
|                                                 var matrix = SKMatrix.MakeScale(1, -1); | ||||
|                                                 matrix.SetScaleTranslate(1, -1, 0, flipped.Height); | ||||
|                                                 gradient.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(0, flipped.Height), new[] { new SKColor(0, 0, 0, 0), SKColors.Black }, null, SKShaderTileMode.Clamp, matrix); | ||||
|                                                 canvas.DrawBitmap(flipped, (horizontalImagePadding * (i + 1)) + (iSlice * i), iHeight + verticalSpacing, gradient); | ||||
|                                             } | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     imageIndex++; | ||||
| 
 | ||||
|                     if (imageIndex >= paths.Length) | ||||
|                         imageIndex = 0; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return bitmap; | ||||
|         } | ||||
| 
 | ||||
|         private SKBitmap BuildSquareCollageBitmap(string[] paths, int width, int height) | ||||
|         { | ||||
|             var bitmap = new SKBitmap(width, height); | ||||
|             var imageIndex = 0; | ||||
|             var cellWidth = width / 2; | ||||
|             var cellHeight = height / 2; | ||||
| 
 | ||||
|             using (var canvas = new SKCanvas(bitmap)) | ||||
|             { | ||||
|                 for (var x = 0; x < 2; x++) | ||||
|                 { | ||||
|                     for (var y = 0; y < 2; y++) | ||||
|                     { | ||||
|                         using (var currentBitmap = SKBitmap.Decode(paths[imageIndex])) | ||||
|                         { | ||||
|                             using (var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) | ||||
|                             { | ||||
|                                 // scale image | ||||
|                                 currentBitmap.Resize(resizedBitmap, SKBitmapResizeMethod.Lanczos3); | ||||
| 
 | ||||
|                                 // draw this image into the strip at the next position | ||||
|                                 var xPos = x * cellWidth; | ||||
|                                 var yPos = y * cellHeight; | ||||
|                                 canvas.DrawBitmap(resizedBitmap, xPos, yPos); | ||||
|                             } | ||||
|                         } | ||||
|                         imageIndex++; | ||||
| 
 | ||||
|                         if (imageIndex >= paths.Length) | ||||
|                             imageIndex = 0; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return bitmap; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										68
									
								
								Emby.Drawing.Skia/UnplayedCountIndicator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								Emby.Drawing.Skia/UnplayedCountIndicator.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| using SkiaSharp; | ||||
| using MediaBrowser.Common.Configuration; | ||||
| using MediaBrowser.Common.Net; | ||||
| using MediaBrowser.Model.Drawing; | ||||
| using System.Globalization; | ||||
| using System.Threading.Tasks; | ||||
| using MediaBrowser.Common.IO; | ||||
| using MediaBrowser.Controller.IO; | ||||
| using MediaBrowser.Model.IO; | ||||
| 
 | ||||
| namespace Emby.Drawing.Skia | ||||
| { | ||||
|     public class UnplayedCountIndicator | ||||
|     { | ||||
|         private const int OffsetFromTopRightCorner = 38; | ||||
| 
 | ||||
|         private readonly IApplicationPaths _appPaths; | ||||
|         private readonly IHttpClient _iHttpClient; | ||||
|         private readonly IFileSystem _fileSystem; | ||||
| 
 | ||||
|         public UnplayedCountIndicator(IApplicationPaths appPaths, IHttpClient iHttpClient, IFileSystem fileSystem) | ||||
|         { | ||||
|             _appPaths = appPaths; | ||||
|             _iHttpClient = iHttpClient; | ||||
|             _fileSystem = fileSystem; | ||||
|         } | ||||
| 
 | ||||
|         public void DrawUnplayedCountIndicator(SKCanvas canvas, ImageSize imageSize, int count) | ||||
|         { | ||||
|             var x = imageSize.Width - OffsetFromTopRightCorner; | ||||
|             var text = count.ToString(CultureInfo.InvariantCulture); | ||||
| 
 | ||||
|             using (var paint = new SKPaint()) | ||||
|             { | ||||
|                 paint.Color = SKColor.Parse("#CC52B54B"); | ||||
|                 paint.Style = SKPaintStyle.Fill; | ||||
|                 canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint); | ||||
|             } | ||||
|             using (var paint = new SKPaint()) | ||||
|             { | ||||
|                 paint.Color = new SKColor(255, 255, 255, 255); | ||||
|                 paint.Style = SKPaintStyle.Fill; | ||||
|                 paint.Typeface = SKTypeface.FromFile(PlayedIndicatorDrawer.ExtractFont("robotoregular.ttf", _appPaths, _fileSystem)); | ||||
|                 paint.TextSize = 24; | ||||
|                 paint.IsAntialias = true; | ||||
| 
 | ||||
|                 var y = OffsetFromTopRightCorner + 9; | ||||
| 
 | ||||
|                 if (text.Length == 1) | ||||
|                 { | ||||
|                     x -= 7; | ||||
|                 } | ||||
|                 if (text.Length == 2) | ||||
|                 { | ||||
|                     x -= 13; | ||||
|                 } | ||||
|                 else if (text.Length >= 3) | ||||
|                 { | ||||
|                     x -= 15; | ||||
|                     y -= 2; | ||||
|                     paint.TextSize = 18; | ||||
|                 } | ||||
| 
 | ||||
|                 canvas.DrawText(text, (float)x, y, paint); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -136,14 +136,6 @@ namespace Emby.Drawing | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private string CroppedWhitespaceImageCachePath | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 return Path.Combine(_appPaths.ImageCachePath, "cropped-images"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void AddParts(IEnumerable<IImageEnhancer> enhancers) | ||||
|         { | ||||
|             ImageEnhancers = enhancers.ToArray(); | ||||
| @ -186,14 +178,6 @@ namespace Emby.Drawing | ||||
|                 return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); | ||||
|             } | ||||
| 
 | ||||
|             if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding) | ||||
|             { | ||||
|                 var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); | ||||
| 
 | ||||
|                 originalImagePath = tuple.Item1; | ||||
|                 dateModified = tuple.Item2; | ||||
|             } | ||||
| 
 | ||||
|             if (options.Enhancers.Count > 0) | ||||
|             { | ||||
|                 var tuple = await GetEnhancedImage(new ItemImageInfo | ||||
| @ -400,46 +384,6 @@ namespace Emby.Drawing | ||||
|             return requestedFormat; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Crops whitespace from an image, caches the result, and returns the cached path | ||||
|         /// </summary> | ||||
|         private async Task<Tuple<string, DateTime>> GetWhitespaceCroppedImage(string originalImagePath, DateTime dateModified) | ||||
|         { | ||||
|             var name = originalImagePath; | ||||
|             name += "datemodified=" + dateModified.Ticks; | ||||
| 
 | ||||
|             var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath)); | ||||
| 
 | ||||
|             // Check again in case of contention | ||||
|             if (_fileSystem.FileExists(croppedImagePath)) | ||||
|             { | ||||
|                 return GetResult(croppedImagePath); | ||||
|             } | ||||
| 
 | ||||
|             try | ||||
|             { | ||||
|                 _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(croppedImagePath)); | ||||
|                 var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(croppedImagePath)); | ||||
|                 _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath)); | ||||
| 
 | ||||
|                 _imageEncoder.CropWhiteSpace(originalImagePath, tmpPath); | ||||
|                 CopyFile(tmpPath, croppedImagePath); | ||||
|                 return GetResult(tmpPath); | ||||
|             } | ||||
|             catch (NotImplementedException) | ||||
|             { | ||||
|                 // No need to spam the log with an error message | ||||
|                 return new Tuple<string, DateTime>(originalImagePath, dateModified); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 // We have to have a catch-all here because some of the .net image methods throw a plain old Exception | ||||
|                 _logger.ErrorException("Error cropping image {0}", ex, originalImagePath); | ||||
| 
 | ||||
|                 return new Tuple<string, DateTime>(originalImagePath, dateModified); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private Tuple<string, DateTime> GetResult(string path) | ||||
|         { | ||||
|             return new Tuple<string, DateTime>(path, _fileSystem.GetLastWriteTimeUtc(path)); | ||||
|  | ||||
| @ -761,7 +761,10 @@ namespace Emby.Server.Core | ||||
|                     return null; | ||||
|                 } | ||||
| 
 | ||||
|                 X509Certificate2 localCert = new X509Certificate2(certificateLocation, info.Password); | ||||
|                 // Don't use an empty string password | ||||
|                 var password = string.IsNullOrWhiteSpace(info.Password) ? null : info.Password; | ||||
| 
 | ||||
|                 X509Certificate2 localCert = new X509Certificate2(certificateLocation, password); | ||||
|                 //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; | ||||
|                 if (!localCert.HasPrivateKey) | ||||
|                 { | ||||
|  | ||||
| @ -126,7 +126,7 @@ namespace Emby.Server.Implementations.AppBase | ||||
|             Logger.Info("Saving system configuration"); | ||||
|             var path = CommonApplicationPaths.SystemConfigurationFilePath; | ||||
| 
 | ||||
|             FileSystem.CreateDirectory(Path.GetDirectoryName(path)); | ||||
|             FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path)); | ||||
| 
 | ||||
|             lock (_configurationSyncLock) | ||||
|             { | ||||
| @ -293,7 +293,7 @@ namespace Emby.Server.Implementations.AppBase | ||||
|             _configurations.AddOrUpdate(key, configuration, (k, v) => configuration); | ||||
| 
 | ||||
|             var path = GetConfigurationFile(key); | ||||
|             FileSystem.CreateDirectory(Path.GetDirectoryName(path)); | ||||
|             FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path)); | ||||
| 
 | ||||
|             lock (_configurationSyncLock) | ||||
|             { | ||||
|  | ||||
| @ -47,7 +47,7 @@ namespace Emby.Server.Implementations.AppBase | ||||
|                 // If the file didn't exist before, or if something has changed, re-save | ||||
|                 if (buffer == null || !buffer.SequenceEqual(newBytes)) | ||||
|                 { | ||||
|                     fileSystem.CreateDirectory(Path.GetDirectoryName(path)); | ||||
|                     fileSystem.CreateDirectory(fileSystem.GetDirectoryName(path)); | ||||
| 
 | ||||
|                     // Save it after load in case we got new items | ||||
|                     fileSystem.WriteAllBytes(path, newBytes); | ||||
|  | ||||
| @ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Devices | ||||
| 
 | ||||
|             _libraryMonitor.ReportFileSystemChangeBeginning(path); | ||||
| 
 | ||||
|             _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); | ||||
|             _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); | ||||
| 
 | ||||
|             try | ||||
|             { | ||||
|  | ||||
| @ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Devices | ||||
|         public Task SaveDevice(DeviceInfo device) | ||||
|         { | ||||
|             var path = Path.Combine(GetDevicePath(device.Id), "device.json"); | ||||
|             _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); | ||||
|             _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); | ||||
| 
 | ||||
|             lock (_syncLock) | ||||
|             { | ||||
| @ -180,7 +180,7 @@ namespace Emby.Server.Implementations.Devices | ||||
|         public void AddCameraUpload(string deviceId, LocalFileInfo file) | ||||
|         { | ||||
|             var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); | ||||
|             _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); | ||||
|             _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); | ||||
| 
 | ||||
|             lock (_syncLock) | ||||
|             { | ||||
|  | ||||
| @ -97,7 +97,7 @@ namespace Emby.Server.Implementations.FFMpeg | ||||
|                 else | ||||
|                 { | ||||
|                     info = existingVersion; | ||||
|                     versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath); | ||||
|                     versionedDirectoryPath = _fileSystem.GetDirectoryName(info.EncoderPath); | ||||
|                     excludeFromDeletions.Add(versionedDirectoryPath); | ||||
|                 } | ||||
|             } | ||||
| @ -135,7 +135,7 @@ namespace Emby.Server.Implementations.FFMpeg | ||||
|                     { | ||||
|                         EncoderPath = encoder, | ||||
|                         ProbePath = probe, | ||||
|                         Version = Path.GetFileName(Path.GetDirectoryName(probe)) | ||||
|                         Version = Path.GetFileName(_fileSystem.GetDirectoryName(probe)) | ||||
|                     }; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -1197,6 +1197,7 @@ namespace Emby.Server.Implementations.Library | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     _logger.Info("Post-scan task cancelled: {0}", task.GetType().Name); | ||||
|                     throw; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|  | ||||
| @ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     // Don't clutter the log | ||||
|                     break; | ||||
|                     throw; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|  | ||||
| @ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Validators | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     // Don't clutter the log | ||||
|                     break; | ||||
|                     throw; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|  | ||||
| @ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library.Validators | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     // Don't clutter the log | ||||
|                     break; | ||||
|                     throw; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|  | ||||
| @ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library.Validators | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     // Don't clutter the log | ||||
|                     break; | ||||
|                     throw; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|  | ||||
| @ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Validators | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     // Don't clutter the log | ||||
|                     break; | ||||
|                     throw; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|  | ||||
| @ -26,6 +26,8 @@ namespace Emby.Server.Implementations.Library.Validators | ||||
| 
 | ||||
|             while (yearNumber < maxYear) | ||||
|             { | ||||
|                 cancellationToken.ThrowIfCancellationRequested(); | ||||
| 
 | ||||
|                 try | ||||
|                 { | ||||
|                     var year = _libraryManager.GetYear(yearNumber); | ||||
| @ -35,7 +37,7 @@ namespace Emby.Server.Implementations.Library.Validators | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     // Don't clutter the log | ||||
|                     break; | ||||
|                     throw; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|  | ||||
| @ -16,12 +16,6 @@ namespace MediaBrowser.Controller.Drawing | ||||
|         /// <value>The supported output formats.</value> | ||||
|         ImageFormat[] SupportedOutputFormats { get; } | ||||
|         /// <summary> | ||||
|         /// Crops the white space. | ||||
|         /// </summary> | ||||
|         /// <param name="inputPath">The input path.</param> | ||||
|         /// <param name="outputPath">The output path.</param> | ||||
|         void CropWhiteSpace(string inputPath, string outputPath); | ||||
|         /// <summary> | ||||
|         /// Encodes the image. | ||||
|         /// </summary> | ||||
|         /// <param name="inputPath">The input path.</param> | ||||
|  | ||||
| @ -86,6 +86,7 @@ namespace MediaBrowser.Controller.Drawing | ||||
|                 PercentPlayed.Equals(0) && | ||||
|                 !UnplayedCount.HasValue && | ||||
|                 !Blur.HasValue && | ||||
|                 !CropWhiteSpace && | ||||
|                 string.IsNullOrEmpty(BackgroundColor) && | ||||
|                 string.IsNullOrEmpty(ForegroundLayer); | ||||
|         } | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| using Emby.Drawing; | ||||
| using Emby.Drawing.Net; | ||||
| using Emby.Drawing.ImageMagick; | ||||
| using Emby.Drawing.Skia; | ||||
| using Emby.Server.Core; | ||||
| using Emby.Server.Implementations; | ||||
| using MediaBrowser.Common.Configuration; | ||||
| @ -23,6 +24,15 @@ namespace MediaBrowser.Server.Startup.Common | ||||
|         { | ||||
|             if (!startupOptions.ContainsOption("-enablegdi")) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     //return new SkiaEncoder(logManager.GetLogger("ImageMagick"), appPaths, httpClient, fileSystem); | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|                     logger.Error("Error loading ImageMagick. Will revert to GDI."); | ||||
|                 } | ||||
| 
 | ||||
|                 try | ||||
|                 { | ||||
|                     return new ImageMagickEncoder(logManager.GetLogger("ImageMagick"), appPaths, httpClient, fileSystem); | ||||
|  | ||||
| @ -191,9 +191,11 @@ | ||||
|   <ItemGroup> | ||||
|     <Content Include="..\packages\SkiaSharp.1.57.1\runtimes\win7-x64\native\libSkiaSharp.dll"> | ||||
|       <Link>x64\libSkiaSharp.dll</Link> | ||||
|       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||||
|     </Content> | ||||
|     <Content Include="..\packages\SkiaSharp.1.57.1\runtimes\win7-x86\native\libSkiaSharp.dll"> | ||||
|       <Link>x86\libSkiaSharp.dll</Link> | ||||
|       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||||
|     </Content> | ||||
|     <Content Include="..\Tools\Installation\MediaBrowser.InstallUtil.dll"> | ||||
|       <Link>MediaBrowser.InstallUtil.dll</Link> | ||||
| @ -1110,6 +1112,10 @@ | ||||
|       <Project>{c97a239e-a96c-4d64-a844-ccf8cc30aecb}</Project> | ||||
|       <Name>Emby.Drawing.Net</Name> | ||||
|     </ProjectReference> | ||||
|     <ProjectReference Include="..\Emby.Drawing.Skia\Emby.Drawing.Skia.csproj"> | ||||
|       <Project>{2312da6d-ff86-4597-9777-bceec32d96dd}</Project> | ||||
|       <Name>Emby.Drawing.Skia</Name> | ||||
|     </ProjectReference> | ||||
|     <ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj"> | ||||
|       <Project>{08fff49b-f175-4807-a2b5-73b0ebd9f716}</Project> | ||||
|       <Name>Emby.Drawing</Name> | ||||
|  | ||||
| @ -15,6 +15,7 @@ using System.Text; | ||||
| using System.Text.RegularExpressions; | ||||
| using System.Threading; | ||||
| using System.Xml; | ||||
| using MediaBrowser.Controller.Entities.TV; | ||||
| using MediaBrowser.Model.IO; | ||||
| using MediaBrowser.Model.Xml; | ||||
| 
 | ||||
| @ -227,6 +228,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected virtual string MovieDbParserSearchString | ||||
|         { | ||||
|             get { return "themoviedb.org/movie/"; } | ||||
|         } | ||||
| 
 | ||||
|         private void ParseProviderLinks(T item, string xml) | ||||
|         { | ||||
|             //Look for a match for the Regex pattern "tt" followed by 7 digits | ||||
| @ -238,7 +244,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers | ||||
| 
 | ||||
|             // Support Tmdb | ||||
|             // http://www.themoviedb.org/movie/36557 | ||||
|             var srch = "themoviedb.org/movie/"; | ||||
|             var srch = MovieDbParserSearchString; | ||||
|             var index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase); | ||||
| 
 | ||||
|             if (index != -1) | ||||
| @ -250,6 +256,23 @@ namespace MediaBrowser.XbmcMetadata.Parsers | ||||
|                     item.SetProviderId(MetadataProviders.Tmdb, tmdbId); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (item is Series) | ||||
|             { | ||||
|                 srch = "thetvdb.com/?tab=series&id="; | ||||
| 
 | ||||
|                 index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase); | ||||
| 
 | ||||
|                 if (index != -1) | ||||
|                 { | ||||
|                     var tvdbId = xml.Substring(index + srch.Length).TrimEnd('/'); | ||||
|                     int value; | ||||
|                     if (!string.IsNullOrWhiteSpace(tvdbId) && int.TryParse(tvdbId, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) | ||||
|                     { | ||||
|                         item.SetProviderId(MetadataProviders.Tvdb, tvdbId); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void FetchDataFromXmlNode(XmlReader reader, MetadataResult<T> itemResult) | ||||
|  | ||||
| @ -13,6 +13,19 @@ namespace MediaBrowser.XbmcMetadata.Parsers | ||||
| { | ||||
|     public class SeriesNfoParser : BaseNfoParser<Series> | ||||
|     { | ||||
|         protected override bool SupportsUrlAfterClosingXmlTag | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected override string MovieDbParserSearchString | ||||
|         { | ||||
|             get { return "themoviedb.org/tv/"; } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Fetches the data from XML node. | ||||
|         /// </summary> | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| using System.Reflection; | ||||
| 
 | ||||
| [assembly: AssemblyVersion("3.2.15.2")] | ||||
| [assembly: AssemblyVersion("3.2.15.3")] | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user