diff options
| -rw-r--r-- | Emby.Server.Implementations/Library/IgnorePatterns.cs | 23 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs | 22 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs | 34 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/SkiaCodecException.cs | 2 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/SkiaEncoder.cs | 472 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/SkiaException.cs | 2 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 120 | ||||
| -rw-r--r-- | Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs | 52 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Users/UserManager.cs | 4 | ||||
| -rw-r--r-- | MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs | 3 | ||||
| -rw-r--r-- | MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs | 3 | ||||
| -rw-r--r-- | MediaBrowser.Providers/Plugins/Omdb/Plugin.cs | 3 | ||||
| -rw-r--r-- | MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs | 3 | ||||
| -rw-r--r-- | tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs | 14 |
14 files changed, 310 insertions, 447 deletions
diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index 6e6ef1359..e30a67593 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -18,7 +18,21 @@ namespace Emby.Server.Implementations.Library { "**/small.jpg", "**/albumart.jpg", - "**/*sample*", + + // We have neither non-greedy matching or character group repetitions, working around that here. + // https://github.com/dazinator/DotNet.Glob#patterns + // .*/sample\..{1,5} + "**/sample.?", + "**/sample.??", + "**/sample.???", // Matches sample.mkv + "**/sample.????", // Matches sample.webm + "**/sample.?????", + "**/*.sample.?", + "**/*.sample.??", + "**/*.sample.???", + "**/*.sample.????", + "**/*.sample.?????", + "**/sample/*", // Directories "**/metadata/**", @@ -64,10 +78,13 @@ namespace Emby.Server.Implementations.Library "**/.grab/**", "**/.grab", - // Unix hidden files and directories - "**/.*/**", + // Unix hidden files "**/.*", + // Mac - if you ever remove the above. + // "**/._*", + // "**/.DS_Store", + // thumbs.db "**/thumbs.db", diff --git a/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs b/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs index f2df066ec..6136a2ff9 100644 --- a/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs +++ b/Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs @@ -19,22 +19,18 @@ namespace Jellyfin.Drawing.Skia /// <param name="percent">The percentage played to display with the indicator.</param> public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent) { - using (var paint = new SKPaint()) - { - var endX = imageSize.Width - 1; - var endY = imageSize.Height - 1; + 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); + paint.Color = SKColor.Parse("#99000000"); + paint.Style = SKPaintStyle.Fill; + canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, endX, endY), paint); - double foregroundWidth = endX; - foregroundWidth *= percent; - foregroundWidth /= 100; + double foregroundWidth = (endX * percent) / 100; - paint.Color = SKColor.Parse("#FF00A4DC"); - canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(foregroundWidth), (float)endY), paint); - } + paint.Color = SKColor.Parse("#FF00A4DC"); + canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(foregroundWidth), endY), paint); } } } diff --git a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs index 7eed5f4f7..db4f78398 100644 --- a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs +++ b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs @@ -22,31 +22,27 @@ namespace Jellyfin.Drawing.Skia { var x = imageSize.Width - OffsetFromTopRightCorner; - using (var paint = new SKPaint()) + using var paint = new SKPaint { - paint.Color = SKColor.Parse("#CC00A4DC"); - paint.Style = SKPaintStyle.Fill; - canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint); - } + Color = SKColor.Parse("#CC00A4DC"), + Style = SKPaintStyle.Fill + }; - using (var paint = new SKPaint()) - { - paint.Color = new SKColor(255, 255, 255, 255); - paint.Style = SKPaintStyle.Fill; + canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint); - paint.TextSize = 30; - paint.IsAntialias = true; + paint.Color = new SKColor(255, 255, 255, 255); + paint.TextSize = 30; + paint.IsAntialias = true; - // or: - // var emojiChar = 0x1F680; - const string Text = "✔️"; - var emojiChar = StringUtilities.GetUnicodeCharacterCode(Text, SKTextEncoding.Utf32); + // or: + // var emojiChar = 0x1F680; + const string Text = "✔️"; + var emojiChar = StringUtilities.GetUnicodeCharacterCode(Text, SKTextEncoding.Utf32); - // ask the font manager for a font with that character - paint.Typeface = SKFontManager.Default.MatchCharacter(emojiChar); + // ask the font manager for a font with that character + paint.Typeface = SKFontManager.Default.MatchCharacter(emojiChar); - canvas.DrawText(Text, (float)x - 20, OffsetFromTopRightCorner + 12, paint); - } + canvas.DrawText(Text, (float)x - 20, OffsetFromTopRightCorner + 12, paint); } } } diff --git a/Jellyfin.Drawing.Skia/SkiaCodecException.cs b/Jellyfin.Drawing.Skia/SkiaCodecException.cs index 1d2db5515..9a50a4d62 100644 --- a/Jellyfin.Drawing.Skia/SkiaCodecException.cs +++ b/Jellyfin.Drawing.Skia/SkiaCodecException.cs @@ -12,7 +12,7 @@ namespace Jellyfin.Drawing.Skia /// Initializes a new instance of the <see cref="SkiaCodecException" /> class. /// </summary> /// <param name="result">The non-successful codec result returned by Skia.</param> - public SkiaCodecException(SKCodecResult result) : base() + public SkiaCodecException(SKCodecResult result) { CodecResult = result; } diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index ba9a5809f..8f45ba674 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -29,9 +29,7 @@ namespace Jellyfin.Drawing.Skia /// </summary> /// <param name="logger">The application logger.</param> /// <param name="appPaths">The application paths.</param> - public SkiaEncoder( - ILogger<SkiaEncoder> logger, - IApplicationPaths appPaths) + public SkiaEncoder(ILogger<SkiaEncoder> logger, IApplicationPaths appPaths) { _logger = logger; _appPaths = appPaths; @@ -102,19 +100,14 @@ namespace Jellyfin.Drawing.Skia /// <returns>The converted format.</returns> 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; - } + return selectedFormat switch + { + ImageFormat.Bmp => SKEncodedImageFormat.Bmp, + ImageFormat.Jpg => SKEncodedImageFormat.Jpeg, + ImageFormat.Gif => SKEncodedImageFormat.Gif, + ImageFormat.Webp => SKEncodedImageFormat.Webp, + _ => SKEncodedImageFormat.Png + }; } private static bool IsTransparentRow(SKBitmap bmp, int row) @@ -146,63 +139,34 @@ namespace Jellyfin.Drawing.Skia private SKBitmap CropWhiteSpace(SKBitmap bitmap) { var topmost = 0; - for (int row = 0; row < bitmap.Height; ++row) + while (topmost < bitmap.Height && IsTransparentRow(bitmap, topmost)) { - if (IsTransparentRow(bitmap, row)) - { - topmost = row + 1; - } - else - { - break; - } + topmost++; } int bottommost = bitmap.Height; - for (int row = bitmap.Height - 1; row >= 0; --row) + while (bottommost >= 0 && IsTransparentRow(bitmap, bottommost - 1)) { - if (IsTransparentRow(bitmap, row)) - { - bottommost = row; - } - else - { - break; - } + bottommost--; } - int leftmost = 0, rightmost = bitmap.Width; - for (int col = 0; col < bitmap.Width; ++col) + var leftmost = 0; + while (leftmost < bitmap.Width && IsTransparentColumn(bitmap, leftmost)) { - if (IsTransparentColumn(bitmap, col)) - { - leftmost = col + 1; - } - else - { - break; - } + leftmost++; } - for (int col = bitmap.Width - 1; col >= 0; --col) + var rightmost = bitmap.Width; + while (rightmost >= 0 && IsTransparentColumn(bitmap, rightmost - 1)) { - if (IsTransparentColumn(bitmap, col)) - { - rightmost = col; - } - else - { - break; - } + rightmost--; } 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 image = SKImage.FromBitmap(bitmap); + using var subset = image.Subset(newRect); + return SKBitmap.FromImage(subset); } /// <inheritdoc /> @@ -216,14 +180,12 @@ namespace Jellyfin.Drawing.Skia throw new FileNotFoundException("File not found", path); } - using (var codec = SKCodec.Create(path, out SKCodecResult result)) - { - EnsureSuccess(result); + using var codec = SKCodec.Create(path, out SKCodecResult result); + EnsureSuccess(result); - var info = codec.Info; + var info = codec.Info; - return new ImageDimensions(info.Width, info.Height); - } + return new ImageDimensions(info.Width, info.Height); } /// <inheritdoc /> @@ -253,12 +215,7 @@ namespace Jellyfin.Drawing.Skia } } - if (HasDiacritics(path)) - { - return true; - } - - return false; + return HasDiacritics(path); } private string NormalizePath(string path) @@ -283,25 +240,17 @@ namespace Jellyfin.Drawing.Skia return SKEncodedOrigin.TopLeft; } - switch (orientation.Value) - { - case ImageOrientation.TopRight: - return SKEncodedOrigin.TopRight; - case ImageOrientation.RightTop: - return SKEncodedOrigin.RightTop; - case ImageOrientation.RightBottom: - return SKEncodedOrigin.RightBottom; - case ImageOrientation.LeftTop: - return SKEncodedOrigin.LeftTop; - case ImageOrientation.LeftBottom: - return SKEncodedOrigin.LeftBottom; - case ImageOrientation.BottomRight: - return SKEncodedOrigin.BottomRight; - case ImageOrientation.BottomLeft: - return SKEncodedOrigin.BottomLeft; - default: - return SKEncodedOrigin.TopLeft; - } + return orientation.Value switch + { + ImageOrientation.TopRight => SKEncodedOrigin.TopRight, + ImageOrientation.RightTop => SKEncodedOrigin.RightTop, + ImageOrientation.RightBottom => SKEncodedOrigin.RightBottom, + ImageOrientation.LeftTop => SKEncodedOrigin.LeftTop, + ImageOrientation.LeftBottom => SKEncodedOrigin.LeftBottom, + ImageOrientation.BottomRight => SKEncodedOrigin.BottomRight, + ImageOrientation.BottomLeft => SKEncodedOrigin.BottomLeft, + _ => SKEncodedOrigin.TopLeft + }; } /// <summary> @@ -323,24 +272,22 @@ namespace Jellyfin.Drawing.Skia if (requiresTransparencyHack || forceCleanBitmap) { - using (var codec = SKCodec.Create(NormalizePath(path))) + using var codec = SKCodec.Create(NormalizePath(path)); + if (codec == null) { - if (codec == null) - { - origin = GetSKEncodedOrigin(orientation); - return null; - } + origin = GetSKEncodedOrigin(orientation); + return null; + } - // create the bitmap - var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack); + // create the bitmap + var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack); - // decode - _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels()); + // decode + _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels()); - origin = codec.EncodedOrigin; + origin = codec.EncodedOrigin; - return bitmap; - } + return bitmap; } var resultBitmap = SKBitmap.Decode(NormalizePath(path)); @@ -367,15 +314,8 @@ namespace Jellyfin.Drawing.Skia { if (cropWhitespace) { - using (var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin)) - { - if (bitmap == null) - { - return null; - } - - return CropWhiteSpace(bitmap); - } + using var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin); + return bitmap == null ? null : CropWhiteSpace(bitmap); } return Decode(path, forceAnalyzeBitmap, orientation, out origin); @@ -403,133 +343,55 @@ namespace Jellyfin.Drawing.Skia private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin) { + if (origin == SKEncodedOrigin.Default) + { + return bitmap; + } + + var needsFlip = origin == SKEncodedOrigin.LeftBottom + || origin == SKEncodedOrigin.LeftTop + || origin == SKEncodedOrigin.RightBottom + || origin == SKEncodedOrigin.RightTop; + var rotated = needsFlip + ? new SKBitmap(bitmap.Height, bitmap.Width) + : new SKBitmap(bitmap.Width, bitmap.Height); + using var surface = new SKCanvas(rotated); + var midX = (float)rotated.Width / 2; + var midY = (float)rotated.Height / 2; + switch (origin) { case SKEncodedOrigin.TopRight: - { - var rotated = new SKBitmap(bitmap.Width, bitmap.Height); - using (var surface = new SKCanvas(rotated)) - { - surface.Translate(rotated.Width, 0); - surface.Scale(-1, 1); - surface.DrawBitmap(bitmap, 0, 0); - } - - return rotated; - } - + surface.Scale(-1, 1, midX, midY); + break; case SKEncodedOrigin.BottomRight: - { - var rotated = new SKBitmap(bitmap.Width, bitmap.Height); - using (var surface = new SKCanvas(rotated)) - { - float px = (float)bitmap.Width / 2; - float py = (float)bitmap.Height / 2; - - surface.RotateDegrees(180, px, py); - surface.DrawBitmap(bitmap, 0, 0); - } - - return rotated; - } - + surface.RotateDegrees(180, midX, midY); + break; case SKEncodedOrigin.BottomLeft: - { - var rotated = new SKBitmap(bitmap.Width, bitmap.Height); - using (var surface = new SKCanvas(rotated)) - { - float px = (float)bitmap.Width / 2; - - float py = (float)bitmap.Height / 2; - - surface.Translate(rotated.Width, 0); - surface.Scale(-1, 1); - - surface.RotateDegrees(180, px, py); - surface.DrawBitmap(bitmap, 0, 0); - } - - return rotated; - } - + surface.Scale(1, -1, midX, midY); + break; case SKEncodedOrigin.LeftTop: - { - // TODO: Remove dual canvases, had trouble with flipping - using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width)) - { - using (var surface = new SKCanvas(rotated)) - { - surface.Translate(rotated.Width, 0); - - surface.RotateDegrees(90); - - surface.DrawBitmap(bitmap, 0, 0); - } - - var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height); - using (var flippedCanvas = new SKCanvas(flippedBitmap)) - { - flippedCanvas.Translate(flippedBitmap.Width, 0); - flippedCanvas.Scale(-1, 1); - flippedCanvas.DrawBitmap(rotated, 0, 0); - } - - return flippedBitmap; - } - } - + surface.Translate(0, -rotated.Height); + surface.Scale(1, -1, midX, midY); + surface.RotateDegrees(-90); + break; case SKEncodedOrigin.RightTop: - { - var rotated = new SKBitmap(bitmap.Height, bitmap.Width); - using (var surface = new SKCanvas(rotated)) - { - surface.Translate(rotated.Width, 0); - surface.RotateDegrees(90); - surface.DrawBitmap(bitmap, 0, 0); - } - - return rotated; - } - + surface.Translate(rotated.Width, 0); + surface.RotateDegrees(90); + break; case SKEncodedOrigin.RightBottom: - { - // TODO: Remove dual canvases, had trouble with flipping - using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width)) - { - using (var surface = new SKCanvas(rotated)) - { - surface.Translate(0, rotated.Height); - surface.RotateDegrees(270); - surface.DrawBitmap(bitmap, 0, 0); - } - - var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height); - using (var flippedCanvas = new SKCanvas(flippedBitmap)) - { - flippedCanvas.Translate(flippedBitmap.Width, 0); - flippedCanvas.Scale(-1, 1); - flippedCanvas.DrawBitmap(rotated, 0, 0); - } - - return flippedBitmap; - } - } - + surface.Translate(rotated.Width, 0); + surface.Scale(1, -1, midX, midY); + surface.RotateDegrees(90); + break; case SKEncodedOrigin.LeftBottom: - { - var rotated = new SKBitmap(bitmap.Height, bitmap.Width); - using (var surface = new SKCanvas(rotated)) - { - surface.Translate(0, rotated.Height); - surface.RotateDegrees(270); - surface.DrawBitmap(bitmap, 0, 0); - } - - return rotated; - } - - default: return bitmap; + surface.Translate(0, rotated.Height); + surface.RotateDegrees(-90); + break; } + + surface.DrawBitmap(bitmap, 0, 0); + return rotated; } /// <inheritdoc/> @@ -552,97 +414,87 @@ namespace Jellyfin.Drawing.Skia var blur = options.Blur ?? 0; var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); - using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation)) + using var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation); + if (bitmap == null) { - if (bitmap == null) - { - throw new InvalidDataException($"Skia unable to read image {inputPath}"); - } + throw new InvalidDataException($"Skia unable to read image {inputPath}"); + } - var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height); + var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height); - if (!options.CropWhiteSpace - && options.HasDefaultOptions(inputPath, originalImageSize) - && !autoOrient) - { - // Just spit out the original file if all the options are default - return inputPath; - } + if (!options.CropWhiteSpace + && options.HasDefaultOptions(inputPath, originalImageSize) + && !autoOrient) + { + // Just spit out the original file if all the options are default + return inputPath; + } - var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize); + var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize); - var width = newImageSize.Width; - var height = newImageSize.Height; + var width = newImageSize.Width; + var height = newImageSize.Height; + + using var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType); + // scale image + bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); + + // If all we're doing is resizing then we can stop now + if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) + { + Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + using var outputStream = new SKFileWStream(outputPath); + using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()); + pixmap.Encode(outputStream, skiaOutputFormat, quality); + return outputPath; + } - using (var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType)) + // create bitmap to use for canvas drawing used to draw into bitmap + using var saveBitmap = new SKBitmap(width, height); + 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) + { + // create image from resized bitmap to apply blur + using var paint = new SKPaint(); + using var filter = SKImageFilter.CreateBlur(blur, blur); + 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) + { + if (!double.TryParse(options.ForegroundLayer, out double opacity)) { - // scale image - bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); + opacity = .4; + } - // If all we're doing is resizing then we can stop now - if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) - { - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); - using (var outputStream = new SKFileWStream(outputPath)) - using (var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels())) - { - pixmap.Encode(outputStream, skiaOutputFormat, quality); - return outputPath; - } - } + canvas.DrawColor(new SKColor(0, 0, 0, (byte)((1 - opacity) * 0xFF)), SKBlendMode.SrcOver); + } - // create bitmap to use for canvas drawing used to draw into bitmap - using (var saveBitmap = new SKBitmap(width, height)) // , bitmap.ColorType, bitmap.AlphaType)) - 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) - { - // create image from resized bitmap to apply blur - using (var paint = new SKPaint()) - using (var filter = SKImageFilter.CreateBlur(blur, blur)) - { - 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) - { - if (!double.TryParse(options.ForegroundLayer, out double opacity)) - { - opacity = .4; - } - - canvas.DrawColor(new SKColor(0, 0, 0, (byte)((1 - opacity) * 0xFF)), SKBlendMode.SrcOver); - } - - if (hasIndicator) - { - DrawIndicator(canvas, width, height, options); - } - - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); - using (var outputStream = new SKFileWStream(outputPath)) - { - using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels())) - { - pixmap.Encode(outputStream, skiaOutputFormat, quality); - } - } - } + if (hasIndicator) + { + DrawIndicator(canvas, width, height, options); + } + + Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + using (var outputStream = new SKFileWStream(outputPath)) + { + using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels())) + { + pixmap.Encode(outputStream, skiaOutputFormat, quality); } } diff --git a/Jellyfin.Drawing.Skia/SkiaException.cs b/Jellyfin.Drawing.Skia/SkiaException.cs index 968d3a244..5b272eac5 100644 --- a/Jellyfin.Drawing.Skia/SkiaException.cs +++ b/Jellyfin.Drawing.Skia/SkiaException.cs @@ -10,7 +10,7 @@ namespace Jellyfin.Drawing.Skia /// <summary> /// Initializes a new instance of the <see cref="SkiaException"/> class. /// </summary> - public SkiaException() : base() + public SkiaException() { } diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 61bef90ec..e0ee4a342 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -69,12 +69,10 @@ namespace Jellyfin.Drawing.Skia /// <param name="height">The desired height of the collage.</param> public void BuildSquareCollage(string[] paths, string outputPath, int width, int height) { - using (var bitmap = BuildSquareCollageBitmap(paths, width, height)) - using (var outputStream = new SKFileWStream(outputPath)) - using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) - { - pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); - } + using var bitmap = BuildSquareCollageBitmap(paths, width, height); + using var outputStream = new SKFileWStream(outputPath); + using var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels()); + pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); } /// <summary> @@ -86,56 +84,46 @@ namespace Jellyfin.Drawing.Skia /// <param name="height">The desired height of the collage.</param> public void BuildThumbCollage(string[] paths, string outputPath, int width, int height) { - using (var bitmap = BuildThumbCollageBitmap(paths, width, height)) - using (var outputStream = new SKFileWStream(outputPath)) - using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) - { - pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); - } + using var bitmap = BuildThumbCollageBitmap(paths, width, height); + using var outputStream = new SKFileWStream(outputPath); + using var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels()); + pixmap.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); + using var canvas = new SKCanvas(bitmap); + canvas.Clear(SKColors.Black); - // number of images used in the thumbnail - var iCount = 3; + // number of images used in the thumbnail + var iCount = 3; - // determine sizes for each image that will composited into the final image - var iSlice = Convert.ToInt32(width / iCount); - int iHeight = Convert.ToInt32(height * 1.00); - int imageIndex = 0; - for (int i = 0; i < iCount; i++) + // determine sizes for each image that will composited into the final image + var iSlice = Convert.ToInt32(width / iCount); + int iHeight = Convert.ToInt32(height * 1.00); + int imageIndex = 0; + for (int i = 0; i < iCount; i++) + { + using var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex); + imageIndex = newIndex; + if (currentBitmap == null) { - using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex)) - { - imageIndex = newIndex; - if (currentBitmap == null) - { - continue; - } - - // resize to the same aspect as the original - int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); - using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) - { - currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High); - - // crop image - int ix = Math.Abs((iWidth - iSlice) / 2); - using (var image = SKImage.FromBitmap(resizeBitmap)) - using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight))) - { - // draw image onto canvas - canvas.DrawImage(subset ?? image, iSlice * i, 0); - } - } - } + continue; } + + // resize to the same aspect as the original + int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); + using var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType); + currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High); + + // crop image + int ix = Math.Abs((iWidth - iSlice) / 2); + using var image = SKImage.FromBitmap(resizeBitmap); + using var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)); + // draw image onto canvas + canvas.DrawImage(subset ?? image, iSlice * i, 0); } return bitmap; @@ -176,33 +164,27 @@ namespace Jellyfin.Drawing.Skia var cellWidth = width / 2; var cellHeight = height / 2; - using (var canvas = new SKCanvas(bitmap)) + using var canvas = new SKCanvas(bitmap); + for (var x = 0; x < 2; x++) { - for (var x = 0; x < 2; x++) + for (var y = 0; y < 2; y++) { - for (var y = 0; y < 2; y++) + using var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex); + imageIndex = newIndex; + + if (currentBitmap == null) { - using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex)) - { - imageIndex = newIndex; - - if (currentBitmap == null) - { - continue; - } - - using (var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) - { - // scale image - currentBitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); - - // draw this image into the strip at the next position - var xPos = x * cellWidth; - var yPos = y * cellHeight; - canvas.DrawBitmap(resizedBitmap, xPos, yPos); - } - } + continue; } + + using var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType); + // scale image + currentBitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); + + // draw this image into the strip at the next position + var xPos = x * cellWidth; + var yPos = y * cellHeight; + canvas.DrawBitmap(resizedBitmap, xPos, yPos); } } diff --git a/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs b/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs index cf3dbde2c..58f887c96 100644 --- a/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs +++ b/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs @@ -28,41 +28,37 @@ namespace Jellyfin.Drawing.Skia var x = imageSize.Width - OffsetFromTopRightCorner; var text = count.ToString(CultureInfo.InvariantCulture); - using (var paint = new SKPaint()) + using var paint = new SKPaint { - paint.Color = SKColor.Parse("#CC00A4DC"); - paint.Style = SKPaintStyle.Fill; - canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint); - } - - using (var paint = new SKPaint()) - { - paint.Color = new SKColor(255, 255, 255, 255); - paint.Style = SKPaintStyle.Fill; + Color = SKColor.Parse("#CC00A4DC"), + Style = SKPaintStyle.Fill + }; - paint.TextSize = 24; - paint.IsAntialias = true; + canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint); - var y = OffsetFromTopRightCorner + 9; + paint.Color = new SKColor(255, 255, 255, 255); + paint.TextSize = 24; + paint.IsAntialias = true; - if (text.Length == 1) - { - x -= 7; - } + var y = OffsetFromTopRightCorner + 9; - if (text.Length == 2) - { - x -= 13; - } - else if (text.Length >= 3) - { - x -= 15; - y -= 2; - paint.TextSize = 18; - } + if (text.Length == 1) + { + x -= 7; + } - canvas.DrawText(text, x, y, paint); + if (text.Length == 2) + { + x -= 13; + } + else if (text.Length >= 3) + { + x -= 15; + y -= 2; + paint.TextSize = 18; } + + canvas.DrawText(text, x, y, paint); } } } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index e49a99fe5..eaa6a0a81 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -766,8 +766,8 @@ namespace Jellyfin.Server.Implementations.Users { // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness - // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.) - return Regex.IsMatch(name, @"^[\w\-'._@]*$"); + // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( ) + return Regex.IsMatch(name, @"^[\w\ \-'._@]*$"); } private IAuthenticationProvider GetAuthenticationProvider(User user) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs index cb7c0362e..54054d015 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs @@ -19,6 +19,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public override string Description => "Get artist and album metadata or images from AudioDB."; + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.AudioDb.xml"; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs index a7e6267da..90266e440 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs @@ -23,6 +23,9 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz public const long DefaultRateLimit = 2000u; + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml"; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { diff --git a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs index 4cf5f4ce6..41ca56164 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs @@ -19,6 +19,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb public override string Description => "Get metadata for movies and other video content from OMDb."; + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.Omdb.xml"; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs b/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs index aa5f819f0..e7079ed3c 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs @@ -17,6 +17,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public override string Description => "Get metadata for movies and other video content from TheTVDB."; + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.TheTvdb.xml"; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs index c145ddc9d..b4e6db8f3 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs @@ -9,17 +9,29 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("/media/small.jpg", true)] [InlineData("/media/albumart.jpg", true)] [InlineData("/media/movie.sample.mp4", true)] + [InlineData("/media/movie/sample.mp4", true)] + [InlineData("/media/movie/sample/movie.mp4", true)] + [InlineData("/foo/sample/bar/baz.mkv", false)] + [InlineData("/media/movies/the sample/the sample.mkv", false)] + [InlineData("/media/movies/sampler.mkv", false)] [InlineData("/media/movies/#Recycle/test.txt", true)] [InlineData("/media/movies/#recycle/", true)] [InlineData("/media/movies/#recycle", true)] [InlineData("thumbs.db", true)] [InlineData(@"C:\media\movies\movie.avi", false)] - [InlineData("/media/.hiddendir/file.mp4", true)] + [InlineData("/media/.hiddendir/file.mp4", false)] [InlineData("/media/dir/.hiddenfile.mp4", true)] + [InlineData("/media/dir/._macjunk.mp4", true)] [InlineData("/volume1/video/Series/@eaDir", true)] [InlineData("/volume1/video/Series/@eaDir/file.txt", true)] [InlineData("/directory/@Recycle", true)] [InlineData("/directory/@Recycle/file.mp3", true)] + [InlineData("/media/movies/.@__thumb", true)] + [InlineData("/media/movies/.@__thumb/foo-bar-thumbnail.png", true)] + [InlineData("/media/music/Foo B.A.R./epic.flac", false)] + [InlineData("/media/music/Foo B.A.R", false)] + // This test is pending an upstream fix: https://github.com/dazinator/DotNet.Glob/issues/78 + // [InlineData("/media/music/Foo B.A.R.", false)] public void PathIgnored(string path, bool expected) { Assert.Equal(expected, IgnorePatterns.ShouldIgnore(path)); |
