diff options
| -rw-r--r-- | Emby.Naming/Video/VideoListResolver.cs | 2 | ||||
| -rw-r--r-- | src/Jellyfin.Drawing.Skia/SkiaEncoder.cs | 57 | ||||
| -rw-r--r-- | tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs | 44 |
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); + } } } |
