mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-07 03:48:06 +02:00
commit
6ee9da3717
30 changed files with 862 additions and 100 deletions
|
@ -53,6 +53,11 @@ namespace Emby.Common.Implementations.IO
|
||||||
if (separator == '/')
|
if (separator == '/')
|
||||||
{
|
{
|
||||||
result = result.Replace('\\', '/');
|
result = result.Replace('\\', '/');
|
||||||
|
|
||||||
|
if (result.StartsWith("smb:/", StringComparison.OrdinalIgnoreCase) && !result.StartsWith("smb://", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result = result.Replace("smb:/", "smb://");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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)
|
public ImageSize GetImageSize(string path)
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
@ -150,6 +139,11 @@ namespace Emby.Drawing.ImageMagick
|
||||||
{
|
{
|
||||||
using (var originalImage = new MagickWand(inputPath))
|
using (var originalImage = new MagickWand(inputPath))
|
||||||
{
|
{
|
||||||
|
if (options.CropWhiteSpace)
|
||||||
|
{
|
||||||
|
originalImage.CurrentImage.TrimImage(10);
|
||||||
|
}
|
||||||
|
|
||||||
ScaleImage(originalImage, width, height, options.Blur ?? 0);
|
ScaleImage(originalImage, width, height, options.Blur ?? 0);
|
||||||
|
|
||||||
if (autoOrient)
|
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));
|
return originalImage.CropWhitespace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
using (var outputStream = _fileSystem.GetFileStream(outputPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, false))
|
return Image.FromFile(path);
|
||||||
{
|
|
||||||
croppedImage.Save(System.Drawing.Imaging.ImageFormat.Png, outputStream, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EncodeImage(string inputPath, string cacheFilePath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
|
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;
|
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 newWidth = Convert.ToInt32(width);
|
||||||
var newHeight = Convert.ToInt32(height);
|
var newHeight = Convert.ToInt32(height);
|
||||||
|
|
|
@ -52,7 +52,12 @@
|
||||||
<Compile Include="..\SharedVersion.cs">
|
<Compile Include="..\SharedVersion.cs">
|
||||||
<Link>Properties\SharedVersion.cs</Link>
|
<Link>Properties\SharedVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="PercentPlayedDrawer.cs" />
|
||||||
|
<Compile Include="PlayedIndicatorDrawer.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="SkiaEncoder.cs" />
|
||||||
|
<Compile Include="StripCollageBuilder.cs" />
|
||||||
|
<Compile Include="UnplayedCountIndicator.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="SkiaSharp, Version=1.57.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
<Reference Include="SkiaSharp, Version=1.57.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
||||||
|
@ -61,6 +66,7 @@
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="fonts\robotoregular.ttf" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
<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)
|
public void AddParts(IEnumerable<IImageEnhancer> enhancers)
|
||||||
{
|
{
|
||||||
ImageEnhancers = enhancers.ToArray();
|
ImageEnhancers = enhancers.ToArray();
|
||||||
|
@ -186,14 +178,6 @@ namespace Emby.Drawing
|
||||||
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
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)
|
if (options.Enhancers.Count > 0)
|
||||||
{
|
{
|
||||||
var tuple = await GetEnhancedImage(new ItemImageInfo
|
var tuple = await GetEnhancedImage(new ItemImageInfo
|
||||||
|
@ -400,46 +384,6 @@ namespace Emby.Drawing
|
||||||
return requestedFormat;
|
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)
|
private Tuple<string, DateTime> GetResult(string path)
|
||||||
{
|
{
|
||||||
return new Tuple<string, DateTime>(path, _fileSystem.GetLastWriteTimeUtc(path));
|
return new Tuple<string, DateTime>(path, _fileSystem.GetLastWriteTimeUtc(path));
|
||||||
|
|
|
@ -761,7 +761,10 @@ namespace Emby.Server.Core
|
||||||
return null;
|
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;
|
//localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA;
|
||||||
if (!localCert.HasPrivateKey)
|
if (!localCert.HasPrivateKey)
|
||||||
{
|
{
|
||||||
|
|
|
@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
Logger.Info("Saving system configuration");
|
Logger.Info("Saving system configuration");
|
||||||
var path = CommonApplicationPaths.SystemConfigurationFilePath;
|
var path = CommonApplicationPaths.SystemConfigurationFilePath;
|
||||||
|
|
||||||
FileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path));
|
||||||
|
|
||||||
lock (_configurationSyncLock)
|
lock (_configurationSyncLock)
|
||||||
{
|
{
|
||||||
|
@ -293,7 +293,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
||||||
|
|
||||||
var path = GetConfigurationFile(key);
|
var path = GetConfigurationFile(key);
|
||||||
FileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path));
|
||||||
|
|
||||||
lock (_configurationSyncLock)
|
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 the file didn't exist before, or if something has changed, re-save
|
||||||
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
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
|
// Save it after load in case we got new items
|
||||||
fileSystem.WriteAllBytes(path, newBytes);
|
fileSystem.WriteAllBytes(path, newBytes);
|
||||||
|
|
|
@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
|
|
||||||
_libraryMonitor.ReportFileSystemChangeBeginning(path);
|
_libraryMonitor.ReportFileSystemChangeBeginning(path);
|
||||||
|
|
||||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
public Task SaveDevice(DeviceInfo device)
|
public Task SaveDevice(DeviceInfo device)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(GetDevicePath(device.Id), "device.json");
|
var path = Path.Combine(GetDevicePath(device.Id), "device.json");
|
||||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||||
|
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
|
@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
public void AddCameraUpload(string deviceId, LocalFileInfo file)
|
public void AddCameraUpload(string deviceId, LocalFileInfo file)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
|
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
|
||||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||||
|
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
|
|
|
@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.FFMpeg
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
info = existingVersion;
|
info = existingVersion;
|
||||||
versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
|
versionedDirectoryPath = _fileSystem.GetDirectoryName(info.EncoderPath);
|
||||||
excludeFromDeletions.Add(versionedDirectoryPath);
|
excludeFromDeletions.Add(versionedDirectoryPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.FFMpeg
|
||||||
{
|
{
|
||||||
EncoderPath = encoder,
|
EncoderPath = encoder,
|
||||||
ProbePath = probe,
|
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)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
_logger.Info("Post-scan task cancelled: {0}", task.GetType().Name);
|
_logger.Info("Post-scan task cancelled: {0}", task.GetType().Name);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
// Don't clutter the log
|
// Don't clutter the log
|
||||||
break;
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
// Don't clutter the log
|
// Don't clutter the log
|
||||||
break;
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
// Don't clutter the log
|
// Don't clutter the log
|
||||||
break;
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
// Don't clutter the log
|
// Don't clutter the log
|
||||||
break;
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
// Don't clutter the log
|
// Don't clutter the log
|
||||||
break;
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,6 +26,8 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
|
|
||||||
while (yearNumber < maxYear)
|
while (yearNumber < maxYear)
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var year = _libraryManager.GetYear(yearNumber);
|
var year = _libraryManager.GetYear(yearNumber);
|
||||||
|
@ -35,7 +37,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
// Don't clutter the log
|
// Don't clutter the log
|
||||||
break;
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,12 +16,6 @@ namespace MediaBrowser.Controller.Drawing
|
||||||
/// <value>The supported output formats.</value>
|
/// <value>The supported output formats.</value>
|
||||||
ImageFormat[] SupportedOutputFormats { get; }
|
ImageFormat[] SupportedOutputFormats { get; }
|
||||||
/// <summary>
|
/// <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.
|
/// Encodes the image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="inputPath">The input path.</param>
|
/// <param name="inputPath">The input path.</param>
|
||||||
|
|
|
@ -86,6 +86,7 @@ namespace MediaBrowser.Controller.Drawing
|
||||||
PercentPlayed.Equals(0) &&
|
PercentPlayed.Equals(0) &&
|
||||||
!UnplayedCount.HasValue &&
|
!UnplayedCount.HasValue &&
|
||||||
!Blur.HasValue &&
|
!Blur.HasValue &&
|
||||||
|
!CropWhiteSpace &&
|
||||||
string.IsNullOrEmpty(BackgroundColor) &&
|
string.IsNullOrEmpty(BackgroundColor) &&
|
||||||
string.IsNullOrEmpty(ForegroundLayer);
|
string.IsNullOrEmpty(ForegroundLayer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using Emby.Drawing;
|
using Emby.Drawing;
|
||||||
using Emby.Drawing.Net;
|
using Emby.Drawing.Net;
|
||||||
using Emby.Drawing.ImageMagick;
|
using Emby.Drawing.ImageMagick;
|
||||||
|
using Emby.Drawing.Skia;
|
||||||
using Emby.Server.Core;
|
using Emby.Server.Core;
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
@ -23,6 +24,15 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
{
|
{
|
||||||
if (!startupOptions.ContainsOption("-enablegdi"))
|
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
|
try
|
||||||
{
|
{
|
||||||
return new ImageMagickEncoder(logManager.GetLogger("ImageMagick"), appPaths, httpClient, fileSystem);
|
return new ImageMagickEncoder(logManager.GetLogger("ImageMagick"), appPaths, httpClient, fileSystem);
|
||||||
|
|
|
@ -191,9 +191,11 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\packages\SkiaSharp.1.57.1\runtimes\win7-x64\native\libSkiaSharp.dll">
|
<Content Include="..\packages\SkiaSharp.1.57.1\runtimes\win7-x64\native\libSkiaSharp.dll">
|
||||||
<Link>x64\libSkiaSharp.dll</Link>
|
<Link>x64\libSkiaSharp.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="..\packages\SkiaSharp.1.57.1\runtimes\win7-x86\native\libSkiaSharp.dll">
|
<Content Include="..\packages\SkiaSharp.1.57.1\runtimes\win7-x86\native\libSkiaSharp.dll">
|
||||||
<Link>x86\libSkiaSharp.dll</Link>
|
<Link>x86\libSkiaSharp.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="..\Tools\Installation\MediaBrowser.InstallUtil.dll">
|
<Content Include="..\Tools\Installation\MediaBrowser.InstallUtil.dll">
|
||||||
<Link>MediaBrowser.InstallUtil.dll</Link>
|
<Link>MediaBrowser.InstallUtil.dll</Link>
|
||||||
|
@ -1110,6 +1112,10 @@
|
||||||
<Project>{c97a239e-a96c-4d64-a844-ccf8cc30aecb}</Project>
|
<Project>{c97a239e-a96c-4d64-a844-ccf8cc30aecb}</Project>
|
||||||
<Name>Emby.Drawing.Net</Name>
|
<Name>Emby.Drawing.Net</Name>
|
||||||
</ProjectReference>
|
</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">
|
<ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj">
|
||||||
<Project>{08fff49b-f175-4807-a2b5-73b0ebd9f716}</Project>
|
<Project>{08fff49b-f175-4807-a2b5-73b0ebd9f716}</Project>
|
||||||
<Name>Emby.Drawing</Name>
|
<Name>Emby.Drawing</Name>
|
||||||
|
|
|
@ -15,6 +15,7 @@ using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Xml;
|
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)
|
private void ParseProviderLinks(T item, string xml)
|
||||||
{
|
{
|
||||||
//Look for a match for the Regex pattern "tt" followed by 7 digits
|
//Look for a match for the Regex pattern "tt" followed by 7 digits
|
||||||
|
@ -238,7 +244,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
||||||
|
|
||||||
// Support Tmdb
|
// Support Tmdb
|
||||||
// http://www.themoviedb.org/movie/36557
|
// http://www.themoviedb.org/movie/36557
|
||||||
var srch = "themoviedb.org/movie/";
|
var srch = MovieDbParserSearchString;
|
||||||
var index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
|
var index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
|
@ -250,6 +256,23 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
||||||
item.SetProviderId(MetadataProviders.Tmdb, tmdbId);
|
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)
|
protected virtual void FetchDataFromXmlNode(XmlReader reader, MetadataResult<T> itemResult)
|
||||||
|
|
|
@ -13,6 +13,19 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
||||||
{
|
{
|
||||||
public class SeriesNfoParser : BaseNfoParser<Series>
|
public class SeriesNfoParser : BaseNfoParser<Series>
|
||||||
{
|
{
|
||||||
|
protected override bool SupportsUrlAfterClosingXmlTag
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string MovieDbParserSearchString
|
||||||
|
{
|
||||||
|
get { return "themoviedb.org/tv/"; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches the data from XML node.
|
/// Fetches the data from XML node.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("3.2.15.2")]
|
[assembly: AssemblyVersion("3.2.15.3")]
|
||||||
|
|
Loading…
Reference in a new issue