diff --git a/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs b/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs
index 0a407d64f5..283bcdef05 100644
--- a/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs
+++ b/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs
@@ -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;
diff --git a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs
index 500f57aade..f603c49509 100644
--- a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs
+++ b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs
@@ -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)
diff --git a/Emby.Drawing.Net/GDIImageEncoder.cs b/Emby.Drawing.Net/GDIImageEncoder.cs
index 638415afdc..e710baaa77 100644
--- a/Emby.Drawing.Net/GDIImageEncoder.cs
+++ b/Emby.Drawing.Net/GDIImageEncoder.cs
@@ -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);
diff --git a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj
index 8d1e221d0a..d7b33b9507 100644
--- a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj
+++ b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj
@@ -52,7 +52,12 @@
Properties\SharedVersion.cs
+
+
+
+
+
@@ -61,6 +66,7 @@
+
diff --git a/Emby.Drawing.Skia/PercentPlayedDrawer.cs b/Emby.Drawing.Skia/PercentPlayedDrawer.cs
new file mode 100644
index 0000000000..e291a462b9
--- /dev/null
+++ b/Emby.Drawing.Skia/PercentPlayedDrawer.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs b/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs
new file mode 100644
index 0000000000..9f3a74eb7f
--- /dev/null
+++ b/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs
@@ -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 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()
+
+ }).ConfigureAwait(false);
+
+ fileSystem.CreateDirectory(fileSystem.GetDirectoryName(filePath));
+
+ try
+ {
+ fileSystem.CopyFile(tempPath, filePath, false);
+ }
+ catch (IOException)
+ {
+
+ }
+
+ return tempPath;
+ }
+ }
+}
diff --git a/Emby.Drawing.Skia/SkiaEncoder.cs b/Emby.Drawing.Skia/SkiaEncoder.cs
new file mode 100644
index 0000000000..d52ad47349
--- /dev/null
+++ b/Emby.Drawing.Skia/SkiaEncoder.cs
@@ -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 _httpClientFactory;
+ private readonly IFileSystem _fileSystem;
+
+ public SkiaEncoder(ILogger logger, IApplicationPaths appPaths, Func 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; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Emby.Drawing.Skia/StripCollageBuilder.cs b/Emby.Drawing.Skia/StripCollageBuilder.cs
new file mode 100644
index 0000000000..7f37007697
--- /dev/null
+++ b/Emby.Drawing.Skia/StripCollageBuilder.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Emby.Drawing.Skia/UnplayedCountIndicator.cs b/Emby.Drawing.Skia/UnplayedCountIndicator.cs
new file mode 100644
index 0000000000..f0283ad23e
--- /dev/null
+++ b/Emby.Drawing.Skia/UnplayedCountIndicator.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index bee0e9b69b..accabcf14b 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -136,14 +136,6 @@ namespace Emby.Drawing
}
}
- private string CroppedWhitespaceImageCachePath
- {
- get
- {
- return Path.Combine(_appPaths.ImageCachePath, "cropped-images");
- }
- }
-
public void AddParts(IEnumerable enhancers)
{
ImageEnhancers = enhancers.ToArray();
@@ -186,14 +178,6 @@ namespace Emby.Drawing
return new Tuple(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;
}
- ///
- /// Crops whitespace from an image, caches the result, and returns the cached path
- ///
- private async Task> 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(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(originalImagePath, dateModified);
- }
- }
-
private Tuple GetResult(string path)
{
return new Tuple(path, _fileSystem.GetLastWriteTimeUtc(path));
diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs
index 6a3881caf8..5ceef0754e 100644
--- a/Emby.Server.Core/ApplicationHost.cs
+++ b/Emby.Server.Core/ApplicationHost.cs
@@ -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)
{
diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
index 13874223cc..385b4bd518 100644
--- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
+++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
@@ -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)
{
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
index ad2f459459..d6a41dd67b 100644
--- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -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);
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index 588b42a093..b246ef1962 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Devices
_libraryMonitor.ReportFileSystemChangeBeginning(path);
- _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+ _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
try
{
diff --git a/Emby.Server.Implementations/Devices/DeviceRepository.cs b/Emby.Server.Implementations/Devices/DeviceRepository.cs
index f739765b34..de0dfda2ed 100644
--- a/Emby.Server.Implementations/Devices/DeviceRepository.cs
+++ b/Emby.Server.Implementations/Devices/DeviceRepository.cs
@@ -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)
{
diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs
index 2becebb3d3..3c8ad55fe4 100644
--- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs
+++ b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs
@@ -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))
};
}
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 685c794b76..3c94f97842 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -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)
{
diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
index 643c5970e1..d4be2dabed 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
@@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators
catch (OperationCanceledException)
{
// Don't clutter the log
- break;
+ throw;
}
catch (Exception ex)
{
diff --git a/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs
index b1820bb917..f7fbb93318 100644
--- a/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Validators
catch (OperationCanceledException)
{
// Don't clutter the log
- break;
+ throw;
}
catch (Exception ex)
{
diff --git a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs
index d8956f78a1..d71e77a9a7 100644
--- a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs
@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library.Validators
catch (OperationCanceledException)
{
// Don't clutter the log
- break;
+ throw;
}
catch (Exception ex)
{
diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs
index 983c881b75..98d53c1250 100644
--- a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs
@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library.Validators
catch (OperationCanceledException)
{
// Don't clutter the log
- break;
+ throw;
}
catch (Exception ex)
{
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
index 6faab7bb9d..97b8ff0ac4 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Validators
catch (OperationCanceledException)
{
// Don't clutter the log
- break;
+ throw;
}
catch (Exception ex)
{
diff --git a/Emby.Server.Implementations/Library/Validators/YearsPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/YearsPostScanTask.cs
index ae43c77f0a..4afb4c04a7 100644
--- a/Emby.Server.Implementations/Library/Validators/YearsPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/YearsPostScanTask.cs
@@ -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)
{
diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs
index de1909e542..ecc99caf94 100644
--- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs
+++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs
@@ -16,12 +16,6 @@ namespace MediaBrowser.Controller.Drawing
/// The supported output formats.
ImageFormat[] SupportedOutputFormats { get; }
///
- /// Crops the white space.
- ///
- /// The input path.
- /// The output path.
- void CropWhiteSpace(string inputPath, string outputPath);
- ///
/// Encodes the image.
///
/// The input path.
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
index f4b3d94554..70ac083430 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
@@ -86,6 +86,7 @@ namespace MediaBrowser.Controller.Drawing
PercentPlayed.Equals(0) &&
!UnplayedCount.HasValue &&
!Blur.HasValue &&
+ !CropWhiteSpace &&
string.IsNullOrEmpty(BackgroundColor) &&
string.IsNullOrEmpty(ForegroundLayer);
}
diff --git a/MediaBrowser.ServerApplication/ImageEncoderHelper.cs b/MediaBrowser.ServerApplication/ImageEncoderHelper.cs
index ddbde2f666..99ccdbbe87 100644
--- a/MediaBrowser.ServerApplication/ImageEncoderHelper.cs
+++ b/MediaBrowser.ServerApplication/ImageEncoderHelper.cs
@@ -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);
diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
index a054382908..d632007d20 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -191,9 +191,11 @@
x64\libSkiaSharp.dll
+ PreserveNewest
x86\libSkiaSharp.dll
+ PreserveNewest
MediaBrowser.InstallUtil.dll
@@ -1110,6 +1112,10 @@
{c97a239e-a96c-4d64-a844-ccf8cc30aecb}
Emby.Drawing.Net
+
+ {2312da6d-ff86-4597-9777-bceec32d96dd}
+ Emby.Drawing.Skia
+
{08fff49b-f175-4807-a2b5-73b0ebd9f716}
Emby.Drawing
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index dfd4694c36..d8f7cb57f4 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -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 itemResult)
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
index 98016f4f70..b0db4e6f38 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
@@ -13,6 +13,19 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
public class SeriesNfoParser : BaseNfoParser
{
+ protected override bool SupportsUrlAfterClosingXmlTag
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ protected override string MovieDbParserSearchString
+ {
+ get { return "themoviedb.org/tv/"; }
+ }
+
///
/// Fetches the data from XML node.
///
diff --git a/SharedVersion.cs b/SharedVersion.cs
index 570343be12..8a2e19849b 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,3 +1,3 @@
using System.Reflection;
-[assembly: AssemblyVersion("3.2.15.2")]
+[assembly: AssemblyVersion("3.2.15.3")]