aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs2
-rw-r--r--src/Jellyfin.Drawing.Skia/SkiaEncoder.cs57
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs44
3 files changed, 81 insertions, 22 deletions
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 4247fea0e..a4bfb8d4a 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -217,6 +217,8 @@ namespace Emby.Naming.Video
// The CleanStringParser should have removed common keywords etc.
return testFilename.IsEmpty
|| testFilename[0] == '-'
+ || testFilename[0] == '_'
+ || testFilename[0] == '.'
|| CheckMultiVersionRegex().IsMatch(testFilename);
}
}
diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index c6eab92ea..ade993d92 100644
--- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -25,7 +25,7 @@ public class SkiaEncoder : IImageEncoder
private readonly ILogger<SkiaEncoder> _logger;
private readonly IApplicationPaths _appPaths;
private static readonly SKImageFilter _imageFilter;
- private static readonly SKTypeface[] _typefaces;
+ private static readonly SKTypeface?[] _typefaces = InitializeTypefaces();
/// <summary>
/// The default sampling options, equivalent to old high quality filter settings when upscaling.
@@ -37,9 +37,7 @@ public class SkiaEncoder : IImageEncoder
/// </summary>
public static readonly SKSamplingOptions DefaultSamplingOptions;
-#pragma warning disable CA1810
static SkiaEncoder()
-#pragma warning restore CA1810
{
var kernel = new[]
{
@@ -59,21 +57,6 @@ public class SkiaEncoder : IImageEncoder
SKShaderTileMode.Clamp,
true);
- // Initialize the list of typefaces
- // We have to statically build a list of typefaces because MatchCharacter only accepts a single character or code point
- // But in reality a human-readable character (grapheme cluster) could be multiple code points. For example, πŸš΅πŸ»β€β™€οΈ is a single emoji but 5 code points (U+1F6B5 + U+1F3FB + U+200D + U+2640 + U+FE0F)
- _typefaces =
- [
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'ιΈ‘'), // CJK Simplified Chinese
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'ι›ž'), // CJK Traditional Chinese
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'γƒŽ'), // CJK Japanese
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, '각'), // CJK Korean
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 128169), // Emojis, 128169 is the πŸ’©emoji
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'Χ–'), // Hebrew
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'ي'), // Arabic
- SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright) // Default font
- ];
-
// use cubic for upscaling
UpscaleSamplingOptions = new SKSamplingOptions(SKCubicResampler.Mitchell);
// use bilinear for everything else
@@ -132,7 +115,7 @@ public class SkiaEncoder : IImageEncoder
/// <summary>
/// Gets the default typeface to use.
/// </summary>
- public static SKTypeface DefaultTypeFace => _typefaces.Last();
+ public static SKTypeface? DefaultTypeFace => _typefaces.Last();
/// <summary>
/// Check if the native lib is available.
@@ -153,6 +136,40 @@ public class SkiaEncoder : IImageEncoder
}
/// <summary>
+ /// Initialize the list of typefaces
+ /// We have to statically build a list of typefaces because MatchCharacter only accepts a single character or code point
+ /// But in reality a human-readable character (grapheme cluster) could be multiple code points. For example, πŸš΅πŸ»β€β™€οΈ is a single emoji but 5 code points (U+1F6B5 + U+1F3FB + U+200D + U+2640 + U+FE0F)
+ /// </summary>
+ /// <returns>The list of typefaces.</returns>
+ private static SKTypeface?[] InitializeTypefaces()
+ {
+ int[] chars = [
+ 'ιΈ‘', // CJK Simplified Chinese
+ 'ι›ž', // CJK Traditional Chinese
+ 'γƒŽ', // CJK Japanese
+ '각', // CJK Korean
+ 128169, // Emojis, 128169 is the Pile of Poo (πŸ’©) emoji
+ 'Χ–', // Hebrew
+ 'ي' // Arabic
+ ];
+ var fonts = new List<SKTypeface>(chars.Length + 1);
+ foreach (var ch in chars)
+ {
+ var font = SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, ch);
+ if (font is not null)
+ {
+ fonts.Add(font);
+ }
+ }
+
+ // Default font
+ fonts.Add(SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright)
+ ?? SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'a'));
+
+ return fonts.ToArray();
+ }
+
+ /// <summary>
/// Convert a <see cref="ImageFormat"/> to a <see cref="SKEncodedImageFormat"/>.
/// </summary>
/// <param name="selectedFormat">The format to convert.</param>
@@ -809,7 +826,7 @@ public class SkiaEncoder : IImageEncoder
{
foreach (var typeface in _typefaces)
{
- if (typeface.ContainsGlyphs(c))
+ if (typeface is not null && typeface.ContainsGlyphs(c))
{
return typeface;
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
index 6b1398695..2fb45600b 100644
--- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using Emby.Naming.Common;
@@ -269,8 +270,13 @@ namespace Jellyfin.Naming.Tests.Video
files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Equal(7, result.Count);
- Assert.Empty(result[0].AlternateVersions);
+ Assert.Single(result);
+ Assert.Equal(6, result[0].AlternateVersions.Count);
+
+ // Verify 3D recognition is preserved on alternate versions
+ var hsbs = result[0].AlternateVersions.First(v => v.Path.Contains("3d-hsbs", StringComparison.Ordinal));
+ Assert.True(hsbs.Is3D);
+ Assert.Equal("hsbs", hsbs.Format3D);
}
[Fact]
@@ -435,5 +441,39 @@ namespace Jellyfin.Naming.Tests.Video
Assert.Empty(result);
}
+
+ [Fact]
+ public void Resolve_GivenUnderscoreSeparator_GroupsVersions()
+ {
+ var files = new[]
+ {
+ "/movies/Movie (2020)/Movie (2020)_4K.mkv",
+ "/movies/Movie (2020)/Movie (2020)_1080p.mkv"
+ };
+
+ var result = VideoListResolver.Resolve(
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
+ _namingOptions).ToList();
+
+ Assert.Single(result);
+ Assert.Single(result[0].AlternateVersions);
+ }
+
+ [Fact]
+ public void Resolve_GivenDotSeparator_GroupsVersions()
+ {
+ var files = new[]
+ {
+ "/movies/Movie (2020)/Movie (2020).UHD.mkv",
+ "/movies/Movie (2020)/Movie (2020).1080p.mkv"
+ };
+
+ var result = VideoListResolver.Resolve(
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
+ _namingOptions).ToList();
+
+ Assert.Single(result);
+ Assert.Single(result[0].AlternateVersions);
+ }
}
}